Initial implementation of protocol processing modal [SCI-8661] (#5683)

* Initial implementation of protocol processing modal [SCI-8661]

* Prepare placeholder jobs and integrate notifications [SCI-8661]
This commit is contained in:
artoscinote 2023-07-11 09:27:15 +02:00 committed by GitHub
parent 940ca1f096
commit 4a8582ebc8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 231 additions and 48 deletions

View file

@ -667,28 +667,33 @@ var ProtocolsIndex = (function() {
var importUrl = fileInput.attr('data-import-url'); var importUrl = fileInput.attr('data-import-url');
var teamId = fileInput.attr('data-team-id'); var teamId = fileInput.attr('data-team-id');
var type = fileInput.attr('data-type'); var type = fileInput.attr('data-type');
importProtocolFromFile(
ev.target.files[0],
importUrl,
{ team_id: teamId, type: type },
false,
function(datas) {
var nrSuccessful = 0;
_.each(datas, function(data) {
if (data.status === 'ok') {
nrSuccessful += 1;
}
});
animateSpinner(null, false);
if (nrSuccessful) { if(ev.target.files[0].name.split('.').pop() === 'eln') {
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_ok_html', { count: nrSuccessful }), 'success'); importProtocolFromFile(
reloadTable(); ev.target.files[0],
} else { importUrl,
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_failed'), 'danger'); { team_id: teamId, type: type },
false,
function(datas) {
var nrSuccessful = 0;
_.each(datas, function(data) {
if (data.status === 'ok') {
nrSuccessful += 1;
}
});
animateSpinner(null, false);
if (nrSuccessful) {
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_ok_html', { count: nrSuccessful }), 'success');
reloadTable();
} else {
HelperModule.flashAlertMsg(I18n.t('protocols.index.import_results.message_failed'), 'danger');
}
} }
} );
); } else {
protocolFileImportModal.init(ev.target.files, reloadTable);
}
$(this).val(''); $(this).val('');
}); });
} }

View file

@ -591,7 +591,6 @@ class ProtocolsController < ApplicationController
message_items: { message_items: {
protocol: protocol.id protocol: protocol.id
}) })
generate_import_protocol_notification(current_user, protocol)
format.json do format.json do
render json: { status: :ok }, status: :ok render json: { status: :ok }, status: :ok
end end
@ -918,6 +917,11 @@ class ProtocolsController < ApplicationController
} }
end end
def import_docx
@job = Protocols::DocxImportJob.perform_later(params[:files], current_user)
render json: { job_id: @job.job_id }
end
private private
def set_importer def set_importer
@ -929,30 +933,6 @@ class ProtocolsController < ApplicationController
end end
end end
def generate_import_protocol_notification(user, protocol)
protocol_download_link = "<a data-id='#{protocol.id}' " \
"data-turbolinks='false' " \
"href='#{Rails.application
.routes
.url_helpers
.export_protocols_path(protocol_ids: [protocol.id])}'>" \
"#{export_protocol_file_name([protocol])}</a>"
notification = Notification.create(
type_of: :deliver,
title: I18n.t('protocols.import_export.import_protocol_notification.title', link: protocol_download_link),
message: "#{I18n.t('protocols.import_export.import_protocol_notification.message')} <a data-id='#{protocol.id}' " \
"data-turbolinks='false' " \
"href='#{Rails.application
.routes
.url_helpers
.protocol_path(protocol)}'>" \
"#{protocol.name}</a>"
)
UserNotification.create(notification: notification, user: user)
end
def export_protocol_file_name(protocols) def export_protocol_file_name(protocols)
protocol_name = get_protocol_name(protocols[0]) protocol_name = get_protocol_name(protocols[0])

View file

@ -0,0 +1,18 @@
import TurbolinksAdapter from 'vue-turbolinks';
import Vue from 'vue/dist/vue.esm';
import ProtocolFileImportModal from '../../vue/protocol_import/file_import_modal.vue';
Vue.use(TurbolinksAdapter);
Vue.prototype.i18n = window.I18n;
window.initProtocolFileImportModalComponent = () => {
new Vue({
el: '#protocolFileImportModal',
components: {
'protocol-file-import-modal': ProtocolFileImportModal
}
});
};
initProtocolFileImportModalComponent();

View file

@ -0,0 +1,105 @@
<template>
<div ref="modal" class="modal fade" id="protcolFileImportModal" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">{{ i18n.t(`protocols.import_modal.${state}.title`) }}</h4>
</div>
<div class="modal-body text-xs" v-html="i18n.t(`protocols.import_modal.${state}.body_html`, { url: protocolUrl })">
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-default"
data-dismiss="modal">{{ i18n.t('protocols.import_modal.cancel') }}</button>
<button v-if="state === 'confirm'" type="button"
class="btn btn-primary"
@click.stop.prevent="confirm">{{ i18n.t('protocols.import_modal.import') }}</button>
<button v-else type="button"
class="btn btn-primary"
:disabled="state === 'in_progress'"
@click.stop.prevent="close">{{ i18n.t('protocols.import_modal.close') }}</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ProtocolFileImportModal',
props: {
importUrl: { type: String, required: true}
},
data() {
return {
state: "confirm",
files: null,
jobPollInterval: null,
pollCount: 0,
jobId: null,
protocolUrl: null,
refreshCallback: null
}
},
mounted() {
window.protocolFileImportModal = this;
},
methods: {
open() {
$(this.$refs.modal).modal('show');
},
close() {
if (this.state === "done" && this.refreshCallback) {
this.refreshCallback();
}
$(this.$refs.modal).modal('hide');
},
init(files, refreshCallback) {
this.refreshCallback = refreshCallback;
this.pollCount = 0;
this.jobId = null;
this.files = files;
this.state = "confirm";
this.open();
},
confirm() {
$.post(this.importUrl, (data) => {
this.state = 'in_progress';
this.jobId = data.job_id
this.jobPollInterval = setInterval(this.fetchJobStatus, 1000);
});
},
fetchJobStatus() {
this.pollCount += 1;
if (this.pollCount > 4) {
this.state = 'not_yet_done';
clearInterval(this.jobPollInterval);
return;
}
$.get(`/jobs/${this.jobId}/status`, (data) => {
let status = data.status;
console.log(data)
switch (status) {
case 'pending':
break;
case 'running':
break;
case 'done':
this.state = 'done';
clearInterval(this.jobPollInterval);
break;
case 'failed':
clearInterval(this.jobPollInterval);
break;
}
});
}
}
}
</script>

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
module Protocols
class DocxImportJob < ApplicationJob
def perform(files, user)
ProtocolImporters::DocxService.new(files, user).import!
end
end
end

View file

@ -0,0 +1,43 @@
# frozen_string_literal: true
module ProtocolImporters
class DocxService
def initialize(files, user)
@files = files
@user = user
end
def import!
# TODO: Implement actual logic
ActiveRecord::Base.transaction do
@protocol = @user.current_team
.protocols
.create!(
name: "PARSED PROTOCOL #{rand * 10000}",
protocol_type: :in_repository_draft,
added_by: @user
)
create_notification!
end
end
def create_notification!
# TODO: Add proper protocol original file link
protocol_download_link = "<a data-id='#{@protocol.id}' " \
"data-turbolinks='false' " \
"href='#'>" \
"#{@protocol.name}</a>"
notification = Notification.create(
type_of: :deliver,
title: I18n.t('protocols.import_export.import_protocol_notification.title', link: protocol_download_link),
message: "#{I18n.t('protocols.import_export.import_protocol_notification.message')} " \
"<a data-id='#{@protocol.id}' data-turbolinks='false' " \
"href='#{Rails.application.routes.url_helpers.protocol_path(@protocol)}'>" \
"#{@protocol.name}</a>"
)
UserNotification.create(notification: notification, user: @user)
end
end
end

View file

@ -34,6 +34,11 @@
</div> </div>
<% end %> <% end %>
<div id="protocolFileImportModal">
<protocol-file-import-modal import-url="<%= import_docx_protocols_path %>" />
</div>
<%= javascript_include_tag 'vue_protocol_file_import_modal' %>
<div id="protocolsio-preview-modal-target"></div> <div id="protocolsio-preview-modal-target"></div>
<%= render partial: "protocols/index/general_toolbar.html.erb" %> <%= render partial: "protocols/index/general_toolbar.html.erb" %>
<%= render partial: "protocols/index/protocol_filters.html.erb" %> <%= render partial: "protocols/index/protocol_filters.html.erb" %>

View file

@ -22,7 +22,7 @@
<li> <li>
<a class="btn-link-alt btn-secondary-link btn-open-file" <%= can_create_protocols_in_repository?(@current_team) ? 'data-action="import"' : 'disabled="disabled"' %>> <a class="btn-link-alt btn-secondary-link btn-open-file" <%= can_create_protocols_in_repository?(@current_team) ? 'data-action="import"' : 'disabled="disabled"' %>>
<span><%= t("protocols.index.import_alt") %></span> <span><%= t("protocols.index.import_alt") %></span>
<input type="file" value="" accept=".eln" data-role="import-file-input" <input type="file" value="" accept=".eln,.docx" data-role="import-file-input"
data-team-id="<%= @current_team.id %>" data-import-url="<%= import_protocols_path %>"> data-team-id="<%= @current_team.id %>" data-import-url="<%= import_protocols_path %>">
</a> </a>
</li> </li>

View file

@ -2684,6 +2684,22 @@ en:
button: "Rearrange steps" button: "Rearrange steps"
modal: modal:
title: "Rearrange protocol steps" title: "Rearrange protocol steps"
import_modal:
confirm:
title: "Import protocol"
body_html: "You will start importing selected .docx file into SciNote. The import time may vary according to the size of a file and internet connection."
in_progress:
title: "Importing in progress"
body_html: "The process of importing has started. Please wait..."
not_yet_done:
title: "Importing in progress"
body_html: "The import is taking longer than expected. You'll get a notice in <i class=\"sn-icon sn-icon-notifications\"></i> Notifications when your protocol template is ready. You can close the window and continue with your work."
done:
title: "Importing completed"
body_html: 'The protocol has been successfully imported. You can access the draft of protocol template <a href="%{url}">here.</a>'
import: "Import"
cancel: "Cancel"
close: "Close"
print: print:
title: "Print protocol" title: "Print protocol"
button: "Print" button: "Print"
@ -2866,7 +2882,7 @@ en:
edit: "Edit" edit: "Edit"
clone_btn: "Copy" clone_btn: "Copy"
import: "Import" import: "Import"
import_alt: "From local file" import_alt: "From file (.docx, .eln)"
import_protocols_io: "From Protocols.io" import_protocols_io: "From Protocols.io"
modal_import_json_upload: "Upload" modal_import_json_upload: "Upload"
modal_import_json_title: "Import protocols.io file" modal_import_json_title: "Import protocols.io file"

View file

@ -654,6 +654,7 @@ Rails.application.routes.draw do
post 'restore', to: 'protocols#restore' post 'restore', to: 'protocols#restore'
post 'clone', to: 'protocols#clone' post 'clone', to: 'protocols#clone'
post 'import', to: 'protocols#import' post 'import', to: 'protocols#import'
post 'import_docx', to: 'protocols#import_docx'
post 'protocolsio_import_create', post 'protocolsio_import_create',
to: 'protocols#protocolsio_import_create' to: 'protocols#protocolsio_import_create'
post 'protocolsio_import_save', to: 'protocols#protocolsio_import_save' post 'protocolsio_import_save', to: 'protocols#protocolsio_import_save'

View file

@ -34,7 +34,8 @@ const entryList = {
vue_repository_assign_items_to_task_modal: './app/javascript/packs/vue/assign_items_to_task_modal.js', vue_repository_assign_items_to_task_modal: './app/javascript/packs/vue/assign_items_to_task_modal.js',
vue_navigation_top_menu: './app/javascript/packs/vue/navigation/top_menu.js', vue_navigation_top_menu: './app/javascript/packs/vue/navigation/top_menu.js',
vue_navigation_navigator: './app/javascript/packs/vue/navigation/navigator.js', vue_navigation_navigator: './app/javascript/packs/vue/navigation/navigator.js',
vue_components_action_toolbar: './app/javascript/packs/vue/action_toolbar.js' vue_components_action_toolbar: './app/javascript/packs/vue/action_toolbar.js',
vue_protocol_file_import_modal: './app/javascript/packs/vue/protocol_file_import_modal.js'
} }
// Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949 // Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949