mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Merge branch 'features/inventory-import-improvements' into ai-sci-10261-make-protocol-toolbar-responsive
This commit is contained in:
commit
53a68b4b24
118
Gemfile.lock
118
Gemfile.lock
|
@ -60,47 +60,47 @@ GIT
|
|||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
actioncable (7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activestorage (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
actionmailbox (7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
activejob (= 7.0.8.4)
|
||||
activerecord (= 7.0.8.4)
|
||||
activestorage (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
actionview (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
actionmailer (7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
actionview (= 7.0.8.4)
|
||||
activejob (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (7.0.8.1)
|
||||
actionview (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
actionpack (7.0.8.4)
|
||||
actionview (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
rack (~> 2.0, >= 2.2.4)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activestorage (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
actiontext (7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
activerecord (= 7.0.8.4)
|
||||
activestorage (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
actionview (7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -110,14 +110,14 @@ GEM
|
|||
activemodel (>= 4.1, < 7.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
activejob (7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
activerecord (7.0.8.1)
|
||||
activemodel (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
activemodel (7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
activerecord (7.0.8.4)
|
||||
activemodel (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
activerecord-import (1.4.1)
|
||||
activerecord (>= 4.2)
|
||||
activerecord-session_store (2.1.0)
|
||||
|
@ -127,14 +127,14 @@ GEM
|
|||
multi_json (~> 1.11, >= 1.11.2)
|
||||
rack (>= 2.0.8, < 4)
|
||||
railties (>= 6.1)
|
||||
activestorage (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
activestorage (7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
activejob (= 7.0.8.4)
|
||||
activerecord (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (7.0.8.1)
|
||||
activesupport (7.0.8.4)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
|
@ -246,7 +246,7 @@ GEM
|
|||
combine_pdf (1.0.23)
|
||||
matrix
|
||||
ruby-rc4 (>= 0.1.5)
|
||||
concurrent-ruby (1.2.3)
|
||||
concurrent-ruby (1.3.1)
|
||||
crack (0.4.5)
|
||||
rexml
|
||||
crass (1.0.6)
|
||||
|
@ -370,7 +370,7 @@ GEM
|
|||
httparty (0.21.0)
|
||||
mini_mime (>= 1.0.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (1.14.1)
|
||||
i18n (1.14.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
i18n-js (3.9.2)
|
||||
i18n (>= 0.6.6)
|
||||
|
@ -439,8 +439,8 @@ GEM
|
|||
mime-types-data (3.2023.0218.1)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.6)
|
||||
minitest (5.22.2)
|
||||
mini_portile2 (2.8.7)
|
||||
minitest (5.23.1)
|
||||
msgpack (1.7.1)
|
||||
multi_json (1.15.0)
|
||||
multi_test (1.1.0)
|
||||
|
@ -546,8 +546,8 @@ GEM
|
|||
puma (6.4.2)
|
||||
nio4r (~> 2.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.7.3)
|
||||
rack (2.2.8.1)
|
||||
racc (1.8.0)
|
||||
rack (2.2.9)
|
||||
rack-attack (6.6.1)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (2.0.2)
|
||||
|
@ -563,20 +563,20 @@ GEM
|
|||
rack
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rails (7.0.8.1)
|
||||
actioncable (= 7.0.8.1)
|
||||
actionmailbox (= 7.0.8.1)
|
||||
actionmailer (= 7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
actiontext (= 7.0.8.1)
|
||||
actionview (= 7.0.8.1)
|
||||
activejob (= 7.0.8.1)
|
||||
activemodel (= 7.0.8.1)
|
||||
activerecord (= 7.0.8.1)
|
||||
activestorage (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
rails (7.0.8.4)
|
||||
actioncable (= 7.0.8.4)
|
||||
actionmailbox (= 7.0.8.4)
|
||||
actionmailer (= 7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
actiontext (= 7.0.8.4)
|
||||
actionview (= 7.0.8.4)
|
||||
activejob (= 7.0.8.4)
|
||||
activemodel (= 7.0.8.4)
|
||||
activerecord (= 7.0.8.4)
|
||||
activestorage (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.0.8.1)
|
||||
railties (= 7.0.8.4)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
|
@ -597,9 +597,9 @@ GEM
|
|||
railties (> 3.1)
|
||||
rails_serve_static_assets (0.0.5)
|
||||
rails_stdout_logging (0.0.5)
|
||||
railties (7.0.8.1)
|
||||
actionpack (= 7.0.8.1)
|
||||
activesupport (= 7.0.8.1)
|
||||
railties (7.0.8.4)
|
||||
actionpack (= 7.0.8.4)
|
||||
activesupport (= 7.0.8.4)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
}
|
||||
|
||||
.modal .modal-dialog.modal-lg {
|
||||
@apply w-[900px];
|
||||
@apply max-w-[900px] w-full;
|
||||
}
|
||||
|
||||
.modal.fade .modal-dialog {
|
||||
|
|
|
@ -141,7 +141,7 @@ class AssetSyncController < ApplicationController
|
|||
project = assoc.protocol.in_module? ? assoc.my_module.project : nil
|
||||
when Result
|
||||
type_of = :result_file_added
|
||||
message_items = { result: assoc }
|
||||
message_items = { result: assoc.id }
|
||||
project = assoc.my_module.project
|
||||
end
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ class MyModulesController < ApplicationController
|
|||
}
|
||||
end
|
||||
format.json do
|
||||
render json: @my_module, serializer: Lists::MyModuleSerializer, user: current_user
|
||||
render json: @my_module, serializer: Lists::MyModuleSerializer, controller: self, user: current_user
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -280,7 +280,7 @@ class RepositoriesController < ApplicationController
|
|||
render_403 unless can_create_repository_rows?(@repository)
|
||||
|
||||
unless import_params[:file]
|
||||
repository_response(t('repositories.parse_sheet.errors.no_file_selected'))
|
||||
unprocessable_entity_repository_response(t('repositories.parse_sheet.errors.no_file_selected'))
|
||||
return
|
||||
end
|
||||
begin
|
||||
|
@ -290,16 +290,12 @@ class RepositoriesController < ApplicationController
|
|||
session: session
|
||||
)
|
||||
if parsed_file.too_large?
|
||||
return render json: { error: t('general.file.size_exceeded', file_size: Rails.configuration.x.file_max_size_mb) }, status: :unprocessable_entity
|
||||
render json: { error: t('general.file.size_exceeded', file_size: Rails.configuration.x.file_max_size_mb) },
|
||||
status: :unprocessable_entity
|
||||
elsif parsed_file.has_too_many_rows?
|
||||
return render json: { error: t('repositories.import_records.error_message.items_limit', items_size: Constants::IMPORT_REPOSITORY_ITEMS_LIMIT) }, status: :unprocessable_entity
|
||||
render json: { error: t('repositories.import_records.error_message.items_limit',
|
||||
items_size: Constants::IMPORT_REPOSITORY_ITEMS_LIMIT) }, status: :unprocessable_entity
|
||||
else
|
||||
sheet = SpreadsheetParser.open_spreadsheet(import_params[:file])
|
||||
duplicate_ids = SpreadsheetParser.duplicate_ids(sheet)
|
||||
if duplicate_ids.any?
|
||||
@importing_duplicates_warning = t('repositories.import_records.error_message.importing_duplicates', duplicate_ids: duplicate_ids)
|
||||
end
|
||||
|
||||
@import_data = parsed_file.data
|
||||
|
||||
if @import_data.header.blank? || @import_data.columns.blank?
|
||||
|
@ -307,64 +303,47 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
if (@temp_file = parsed_file.generate_temp_file)
|
||||
render json: {
|
||||
import_data: @import_data,
|
||||
temp_file: @temp_file
|
||||
}
|
||||
render json: { import_data: @import_data, temp_file: @temp_file }
|
||||
else
|
||||
return render json: { error: t('repositories.parse_sheet.errors.temp_file_failure') }, status: :unprocessable_entity
|
||||
render json: { error: t('repositories.parse_sheet.errors.temp_file_failure') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
rescue ArgumentError, CSV::MalformedCSVError
|
||||
return render json: { error: t('repositories.parse_sheet.errors.invalid_file', encoding: ''.encoding) }, status: :unprocessable_entity
|
||||
render json: { error: t('repositories.parse_sheet.errors.invalid_file', encoding: ''.encoding) },
|
||||
status: :unprocessable_entity
|
||||
rescue TypeError
|
||||
return render json: { error: t('repositories.parse_sheet.errors.invalid_extension') }, status: :unprocessable_entity
|
||||
render json: { error: t('repositories.parse_sheet.errors.invalid_extension') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def import_records
|
||||
render_403 unless can_create_repository_rows?(Repository.accessible_by_teams(current_team)
|
||||
.find_by_id(import_params[:id]))
|
||||
|
||||
# Access the checkbox values from params
|
||||
can_edit_existing_items = params[:can_edit_existing_items]
|
||||
should_overwrite_with_empty_cells = params[:should_overwrite_with_empty_cells]
|
||||
preview = params[:preview]
|
||||
|
||||
.find_by(id: import_params[:id]))
|
||||
# Check if there exist mapping for repository record (it's mandatory)
|
||||
if import_params[:mappings].present? && import_params[:mappings].value?('-1')
|
||||
import_records = repostiory_import_actions
|
||||
status = import_records.import!(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
status = ImportRepository::ImportRecords
|
||||
.new(
|
||||
temp_file: TempFile.find_by(id: import_params[:file_id]),
|
||||
repository: Repository.accessible_by_teams(current_team).find_by(id: import_params[:id]),
|
||||
mappings: import_params[:mappings],
|
||||
session: session,
|
||||
user: current_user,
|
||||
can_edit_existing_items: import_params[:can_edit_existing_items],
|
||||
should_overwrite_with_empty_cells: import_params[:should_overwrite_with_empty_cells],
|
||||
preview: import_params[:preview]
|
||||
).import!
|
||||
message = t('repositories.import_records.partial_success_flash',
|
||||
nr: status[:nr_of_added], total_nr: status[:total_nr])
|
||||
|
||||
if status[:status] == :ok
|
||||
log_activity(:import_inventory_items,
|
||||
num_of_items: status[:nr_of_added])
|
||||
|
||||
flash[:success] = t('repositories.import_records.success_flash',
|
||||
number_of_rows: status[:nr_of_added],
|
||||
total_nr: status[:total_nr])
|
||||
|
||||
if preview
|
||||
render json: status, status: :ok
|
||||
else
|
||||
render json: {}, status: :ok
|
||||
end
|
||||
|
||||
log_activity(:import_inventory_items, num_of_items: status[:nr_of_added])
|
||||
render json: import_params[:preview] ? status : { message: message }, status: :ok
|
||||
else
|
||||
flash[:alert] =
|
||||
t('repositories.import_records.partial_success_flash',
|
||||
nr: status[:nr_of_added], total_nr: status[:total_nr])
|
||||
render json: {}, status: :unprocessable_entity
|
||||
render json: { message: message }, status: :unprocessable_entity
|
||||
end
|
||||
else
|
||||
render json: {
|
||||
html: render_to_string(
|
||||
partial: 'shared/flash_errors',
|
||||
formats: :html,
|
||||
locals: { error_title: t('repositories.import_records.error_message.errors_list_title'),
|
||||
error: t('repositories.import_records.error_message.no_repository_name') }
|
||||
)
|
||||
}, status: :unprocessable_entity
|
||||
render json: { error: t('repositories.import_records.error_message.mapping_error') },
|
||||
status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -387,8 +366,7 @@ class RepositoriesController < ApplicationController
|
|||
row_ids: params[:row_ids],
|
||||
header_ids: params[:header_ids]
|
||||
},
|
||||
file_type: params[:empty_export] == '1' ? 'csv' : params[:file_type],
|
||||
empty_export: params[:empty_export] == '1'
|
||||
file_type: params[:file_type]
|
||||
)
|
||||
update_user_export_file_type if current_user.settings[:repository_export_file_type] != params[:file_type]
|
||||
log_activity(:export_inventory_items)
|
||||
|
@ -480,16 +458,6 @@ class RepositoriesController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def repostiory_import_actions
|
||||
ImportRepository::ImportRecords.new(
|
||||
temp_file: TempFile.find_by_id(import_params[:file_id]),
|
||||
repository: Repository.accessible_by_teams(current_team).find_by_id(import_params[:id]),
|
||||
mappings: import_params[:mappings],
|
||||
session: session,
|
||||
user: current_user
|
||||
)
|
||||
end
|
||||
|
||||
def load_repository
|
||||
repository_id = params[:id] || params[:repository_id]
|
||||
@repository = Repository.accessible_by_teams(current_user.teams).find_by(id: repository_id)
|
||||
|
@ -571,7 +539,8 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
|
||||
def import_params
|
||||
params.permit(:id, :file, :file_id, :preview, mappings: {}).to_h
|
||||
params.permit(:id, :file, :file_id, :preview, :can_edit_existing_items,
|
||||
:should_overwrite_with_empty_cells, :preview, mappings: {}).to_h
|
||||
end
|
||||
|
||||
def repository_response(message)
|
||||
|
|
|
@ -5,15 +5,18 @@ class ResultOrderableElementsController < ApplicationController
|
|||
before_action :check_manage_permissions
|
||||
|
||||
def reorder
|
||||
position_changed = false
|
||||
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)
|
||||
position_changed ||= result_element.insert_at(position)
|
||||
end
|
||||
end
|
||||
|
||||
log_activity(:result_content_rearranged, @my_module.experiment.project, my_module: @my_module.id)
|
||||
@result.touch
|
||||
if position_changed
|
||||
log_activity(:result_content_rearranged, @my_module.experiment.project, my_module: @my_module.id)
|
||||
@result.touch
|
||||
end
|
||||
|
||||
render json: params[:result_orderable_element_positions], status: :ok
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
|
|
|
@ -122,7 +122,7 @@ class ResultsController < ApplicationController
|
|||
|
||||
def destroy
|
||||
name = @result.name
|
||||
if @result.discard
|
||||
if @result.destroy
|
||||
log_activity(:destroy_result, { destroyed_result: name })
|
||||
render json: {}, status: :ok
|
||||
else
|
||||
|
|
|
@ -6,16 +6,23 @@ class StepOrderableElementsController < ApplicationController
|
|||
|
||||
def reorder
|
||||
@step.with_lock do
|
||||
position_changed = false
|
||||
params[:step_orderable_element_positions].each do |id, position|
|
||||
@step.step_orderable_elements.find(id).update_column(:position, position)
|
||||
step_element = @step.step_orderable_elements.find(id)
|
||||
if step_element.position != position
|
||||
position_changed = true
|
||||
step_element.update_column(:position, position)
|
||||
end
|
||||
end
|
||||
|
||||
if @protocol.in_module?
|
||||
log_activity(:task_step_content_rearranged, @my_module.experiment.project, my_module: @my_module.id)
|
||||
else
|
||||
log_activity(:protocol_step_content_rearranged, nil, protocol: @protocol.id)
|
||||
if position_changed
|
||||
if @protocol.in_module?
|
||||
log_activity(:task_step_content_rearranged, @my_module.experiment.project, my_module: @my_module.id)
|
||||
else
|
||||
log_activity(:protocol_step_content_rearranged, nil, protocol: @protocol.id)
|
||||
end
|
||||
@step.touch
|
||||
end
|
||||
@step.touch
|
||||
end
|
||||
|
||||
render json: params[:step_orderable_element_positions], status: :ok
|
||||
|
|
|
@ -241,16 +241,23 @@ class StepsController < ApplicationController
|
|||
|
||||
def reorder
|
||||
@protocol.with_lock do
|
||||
position_changed = false
|
||||
params[:step_positions].each do |id, position|
|
||||
@protocol.steps.find(id).update_column(:position, position)
|
||||
step = @protocol.steps.find(id)
|
||||
if position != step.position
|
||||
position_changed = true
|
||||
step.update_column(:position, position)
|
||||
end
|
||||
end
|
||||
|
||||
if @protocol.in_module?
|
||||
log_activity(:task_steps_rearranged, @my_module.experiment.project, my_module: @my_module.id)
|
||||
else
|
||||
log_activity(:protocol_steps_rearranged, nil, protocol: @protocol.id)
|
||||
if position_changed
|
||||
if @protocol.in_module?
|
||||
log_activity(:task_steps_rearranged, @my_module.experiment.project, my_module: @my_module.id)
|
||||
else
|
||||
log_activity(:protocol_steps_rearranged, nil, protocol: @protocol.id)
|
||||
end
|
||||
@protocol.touch
|
||||
end
|
||||
@protocol.touch
|
||||
end
|
||||
|
||||
render json: {
|
||||
|
|
|
@ -8,8 +8,8 @@ const app = createApp({
|
|||
data() {
|
||||
return {
|
||||
myModuleParams: null,
|
||||
myModuleUrl: null,
|
||||
tagsModalOpen: false,
|
||||
tagsUrl: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -20,6 +20,7 @@ const app = createApp({
|
|||
},
|
||||
methods: {
|
||||
open(myModuleUrl) {
|
||||
this.myModuleUrl = myModuleUrl;
|
||||
$.ajax({
|
||||
url: myModuleUrl,
|
||||
type: 'GET',
|
||||
|
@ -32,6 +33,7 @@ const app = createApp({
|
|||
},
|
||||
close() {
|
||||
this.myModuleParams = null;
|
||||
this.myModuleUrl = null;
|
||||
this.tagsModalOpen = false;
|
||||
},
|
||||
syncTags(tags) {
|
||||
|
@ -52,15 +54,13 @@ const app = createApp({
|
|||
// Canvas
|
||||
if ($('#canvas-container').length) {
|
||||
$.ajax({
|
||||
url: this.tagsUrl,
|
||||
url: this.myModuleUrl,
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
success(data) {
|
||||
$.each(data.my_modules, (index, myModule) => {
|
||||
$(`div.panel[data-module-id='${myModule.id}']`)
|
||||
.find('.edit-tags-link')
|
||||
.html(myModule.tags_html);
|
||||
});
|
||||
$(`div.panel[data-module-id='${data.data.id}']`)
|
||||
.find('.edit-tags-link')
|
||||
.html(data.data.attributes.tags_html);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
:optionsUrl="projectsUrl"
|
||||
:searchable="true"
|
||||
:value="selectedProject"
|
||||
:optionRenderer="newProjectRenderer"
|
||||
@change="changeProject"
|
||||
/>
|
||||
</div>
|
||||
|
@ -44,6 +45,7 @@
|
|||
:disabled="!(selectedProject != null && selectedProject >= 0)"
|
||||
:searchable="true"
|
||||
:value="selectedExperiment"
|
||||
:optionRenderer="newExperimentRenderer"
|
||||
@change="changeExperiment"
|
||||
/>
|
||||
</div>
|
||||
|
@ -157,6 +159,18 @@ export default {
|
|||
this.selectedExperiment = value;
|
||||
this.newExperimentName = label;
|
||||
},
|
||||
newProjectRenderer(option) {
|
||||
if (option[0] > 0) {
|
||||
return option[1];
|
||||
}
|
||||
return this.i18n.t('dashboard.create_task_modal.new_project', { name: option[1] });
|
||||
},
|
||||
newExperimentRenderer(option) {
|
||||
if (option[0] > 0) {
|
||||
return option[1];
|
||||
}
|
||||
return this.i18n.t('dashboard.create_task_modal.new_experiment', { name: option[1] });
|
||||
},
|
||||
closeModal() {
|
||||
$('#create-task-modal').modal('hide');
|
||||
this.taskName = '';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
position="right"
|
||||
@dtEvent="changeSort"
|
||||
btnIcon="sn-icon sn-icon-sort-down"
|
||||
:e2eSortButton="e2eSortButton"
|
||||
:dataE2e="e2eSortButton"
|
||||
></MenuDropdown>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -87,9 +87,14 @@
|
|||
<div :class="inRepository ? 'protocol-section protocol-information' : ''">
|
||||
<div v-if="inRepository" id="protocol-description" class="protocol-section-header">
|
||||
<div class="protocol-description-container">
|
||||
<a class="protocol-section-caret" role="button" data-toggle="collapse" href="#protocol-description-container" aria-expanded="false" aria-controls="protocol-description-container">
|
||||
<a class="protocol-section-caret"
|
||||
role="button"
|
||||
data-toggle="collapse"
|
||||
href="#protocol-description-container"
|
||||
aria-expanded="false"
|
||||
aria-controls="protocol-description-container">
|
||||
<i class="sn-icon sn-icon-right"></i>
|
||||
<span id="protocolDescriptionLabel" class="protocol-section-title">
|
||||
<span id="protocolDescriptionLabel" class="protocol-section-title" data-e2e="e2e-TX-protocolTemplates-protocolDescription-title">
|
||||
<h2>
|
||||
{{ i18n.t("protocols.header.protocol_description") }}
|
||||
</h2>
|
||||
|
@ -97,7 +102,10 @@
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="protocol-description-container" class="text-base" :class=" inRepository ? 'protocol-description collapse in' : ''" >
|
||||
<div id="protocol-description-container"
|
||||
class="text-base"
|
||||
:class=" inRepository ? 'protocol-description collapse in' : ''"
|
||||
data-e2e="e2e-IF-protocolTemplates-protocolDescription-content">
|
||||
<div v-if="urls.update_protocol_description_url">
|
||||
<Tinymce
|
||||
:value="protocol.attributes.description"
|
||||
|
@ -125,7 +133,7 @@
|
|||
<div class="protocol-steps-container">
|
||||
<a class="protocol-section-caret" role="button" data-toggle="collapse" href="#protocol-steps-container" aria-expanded="false" aria-controls="protocol-steps-container">
|
||||
<i class="sn-icon sn-icon-right"></i>
|
||||
<span id="protocolStepsLabel" class="protocol-section-title">
|
||||
<span id="protocolStepsLabel" class="protocol-section-title" data-e2e="e2e-TX-protocol-templateSteps-title">
|
||||
<h2>
|
||||
{{ i18n.t("protocols.header.protocol_steps") }}
|
||||
</h2>
|
||||
|
@ -139,6 +147,7 @@
|
|||
<a
|
||||
class="btn btn-secondary"
|
||||
:title="i18n.t('protocols.steps.new_step_title')"
|
||||
data-e2e="e2e-BT-protocol-templateSteps-newStepTop"
|
||||
@keyup.enter="addStep(steps.length)"
|
||||
@click="addStep(steps.length)"
|
||||
tabindex="0">
|
||||
|
@ -146,17 +155,18 @@
|
|||
<span>{{ i18n.t("protocols.steps.new_step") }}</span>
|
||||
</a>
|
||||
<div v-if="steps.length > 0" class="flex justify-between items-center gap-4">
|
||||
<button @click="collapseSteps" class="btn btn-secondary flex px-4" tabindex="0">
|
||||
<button @click="collapseSteps" class="btn btn-secondary flex px-4" tabindex="0" data-e2e="e2e-BT-protocol-templateSteps-collapse">
|
||||
<i class="sn-icon sn-icon-collapse-all"></i>
|
||||
{{ i18n.t("protocols.steps.collapse_label") }}
|
||||
</button>
|
||||
<button @click="expandSteps" class="btn btn-secondary flex px-4" tabindex="0">
|
||||
<button @click="expandSteps" class="btn btn-secondary flex px-4" tabindex="0" data-e2e="e2e-BT-protocol-templateSteps-expand">
|
||||
<i class="sn-icon sn-icon-expand-all"></i>
|
||||
{{ i18n.t("protocols.steps.expand_label") }}
|
||||
</button>
|
||||
<a v-if="steps.length > 0 && urls.reorder_steps_url"
|
||||
class="btn btn-light icon-btn"
|
||||
data-toggle="modal"
|
||||
data-e2e="e2e-BT-protocol-templateSteps-reorder"
|
||||
@click="startStepReorder"
|
||||
@keyup.enter="startStepReorder"
|
||||
:class="{'disabled': steps.length == 1}"
|
||||
|
@ -167,7 +177,7 @@
|
|||
</div>
|
||||
<div class="protocol-steps pb-8">
|
||||
<div v-for="(step, index) in steps" :key="step.id" class="step-block">
|
||||
<div v-if="index > 0 && urls.add_step_url" class="insert-step" @click="addStep(index)">
|
||||
<div v-if="index > 0 && urls.add_step_url" class="insert-step" @click="addStep(index)" data-e2e="e2e-BT-protocol-templateSteps-insertStep">
|
||||
<i class="sn-icon sn-icon-new-task"></i>
|
||||
</div>
|
||||
<Step
|
||||
|
@ -191,7 +201,7 @@
|
|||
:userSettingsUrl="userSettingsUrl"
|
||||
:assignableMyModuleId="protocol.attributes.assignable_my_module_id"
|
||||
/>
|
||||
<div v-if="(index === steps.length - 1) && urls.add_step_url" class="insert-step" @click="addStep(index + 1)">
|
||||
<div v-if="(index === steps.length - 1) && urls.add_step_url" class="insert-step" @click="addStep(index + 1)" data-e2e="e2e-BT-protocol-templateSteps-insertStep">
|
||||
<i class="sn-icon sn-icon-new-task"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -199,6 +209,7 @@
|
|||
<a
|
||||
class="btn btn-secondary"
|
||||
:title="i18n.t('protocols.steps.new_step_title')"
|
||||
data-e2e="e2e-BT-protocol-templateSteps-newStepBottom"
|
||||
@keyup.enter="addStep(steps.length)"
|
||||
@click="addStep(steps.length)"
|
||||
tabindex="0">
|
||||
|
|
|
@ -1,32 +1,33 @@
|
|||
<template>
|
||||
<div class="protocol-section protocol-information mb-4">
|
||||
<div class="protocol-section protocol-information mb-4" data-e2e="e2e-CO-protocolTemplates-protocolDetails">
|
||||
<div id="protocol-details" class="protocol-section-header">
|
||||
<div class="protocol-details-container">
|
||||
<a class="protocol-section-caret" role="button" data-toggle="collapse"
|
||||
href="#details-container" aria-expanded="false" aria-controls="details-container">
|
||||
<i class="sn-icon sn-icon-right"></i>
|
||||
<span id="protocolDetailsLabel" class="protocol-section-title">
|
||||
<h2>
|
||||
<h2 data-e2e="e2e-TX-protocolTemplates-protocolDetails-title">
|
||||
{{ i18n.t("protocols.header.details") }}
|
||||
</h2>
|
||||
<span class="protocol-code" >{{ protocol.attributes.code }}</span>
|
||||
<span class="protocol-code" data-e2e="e2e-TX-protocolTemplates-protocolDetails-protocolId">{{ protocol.attributes.code }}</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="actions-block">
|
||||
<a class="btn btn-light icon-btn pull-right"
|
||||
:href="protocol.attributes.urls.print_protocol_url" target="_blank">
|
||||
:href="protocol.attributes.urls.print_protocol_url" target="_blank"
|
||||
data-e2e="e2e-BT-protocolTemplates-protocolDetails-print">
|
||||
<span class="sn-icon sn-icon-printer" aria-hidden="true"></span>
|
||||
</a>
|
||||
<button class="btn btn-light" @click="openVersionsModal">
|
||||
<button class="btn btn-light" @click="openVersionsModal" data-e2e="e2e-BT-protocolTemplates-protocolDetails-versions">
|
||||
{{ i18n.t("protocols.header.versions") }}
|
||||
</button>
|
||||
<button v-if="protocol.attributes.urls.publish_url"
|
||||
@click="$emit('publish')" class="btn btn-primary">
|
||||
@click="$emit('publish')" class="btn btn-primary" data-e2e="e2e-BT-protocolTemplates-protocolDetails-publish">
|
||||
{{ i18n.t("protocols.header.publish") }}</button>
|
||||
<button v-if="protocol.attributes.urls.save_as_draft_url"
|
||||
:disabled="protocol.attributes.has_draft || creatingDraft"
|
||||
@click="saveAsdraft" class="btn btn-secondary">
|
||||
@click="saveAsdraft" class="btn btn-secondary" data-e2e="e2e-BT-protocolTemplates-protocolDetails-saveAsDraft">
|
||||
{{ i18n.t("protocols.header.save_as_draft") }}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -34,33 +35,33 @@
|
|||
<div id="details-container" class="protocol-details collapse in">
|
||||
<div class="protocol-metadata">
|
||||
<p class="data-block">
|
||||
<span>{{ i18n.t("protocols.header.version") }}</span>
|
||||
<b>{{ titleVersion }}</b>
|
||||
<span data-e2e="e2e-TX-protocolTemplates-protocolDetails-versionLabel">{{ i18n.t("protocols.header.version") }}</span>
|
||||
<b data-e2e="e2e-TX-protocolTemplates-protocolDetails-version">{{ titleVersion }}</b>
|
||||
</p>
|
||||
<p class="data-block" v-if="protocol.attributes.published">
|
||||
<span>{{ i18n.t("protocols.header.published_on") }}</span>
|
||||
<b>{{ protocol.attributes.published_on_formatted }}</b>
|
||||
<span data-e2e="e2e-TX-protocolTemplates-protocolDetails-publishedOnLabel">{{ i18n.t("protocols.header.published_on") }}</span>
|
||||
<b data-e2e="e2e-TX-protocolTemplates-protocolDetails-publishedOn">{{ protocol.attributes.published_on_formatted }}</b>
|
||||
</p>
|
||||
<p class="data-block" v-if="protocol.attributes.published">
|
||||
<p class="data-block" v-if="protocol.attributes.published" data-e2e="e2e-TX-protocolTemplates-protocolDetails-publishedBy">
|
||||
<span>{{ i18n.t("protocols.header.published_by") }}</span>
|
||||
<img :src="protocol.attributes.published_by.avatar" class="rounded-full"/>
|
||||
{{ protocol.attributes.published_by.name }}
|
||||
</p>
|
||||
<p class="data-block">
|
||||
<span>{{ i18n.t("protocols.header.updated_at") }}</span>
|
||||
<b>{{ protocol.attributes.updated_at_formatted }}</b>
|
||||
<span data-e2e="e2e-TX-protocolTemplates-protocolDetails-updatedAtLabel">{{ i18n.t("protocols.header.updated_at") }}</span>
|
||||
<b data-e2e="e2e-TX-protocolTemplates-protocolDetails-updatedAt">{{ protocol.attributes.updated_at_formatted }}</b>
|
||||
</p>
|
||||
<p class="data-block">
|
||||
<span>{{ i18n.t("protocols.header.created_at") }}</span>
|
||||
<b>{{ protocol.attributes.created_at_formatted }}</b>
|
||||
<span data-e2e="e2e-TX-protocolTemplates-protocolDetails-createdAtLabel">{{ i18n.t("protocols.header.created_at") }}</span>
|
||||
<b data-e2e="e2e-TX-protocolTemplates-protocolDetails-createdAt">{{ protocol.attributes.created_at_formatted }}</b>
|
||||
</p>
|
||||
<p class="data-block">
|
||||
<p class="data-block" data-e2e="e2e-TX-protocolTemplates-protocolDetails-createdBy">
|
||||
<span>{{ i18n.t("protocols.header.added_by") }}</span>
|
||||
<img :src="protocol.attributes.added_by.avatar" class="rounded-full"/>
|
||||
{{ protocol.attributes.added_by.name }}
|
||||
</p>
|
||||
<p class="data-block authors-data">
|
||||
<span>{{ i18n.t("protocols.header.authors") }}</span>
|
||||
<span data-e2e="e2e-TX-protocolTemplates-protocolDetails-authorsLabel">{{ i18n.t("protocols.header.authors") }}</span>
|
||||
<span class="authors-list" v-if="protocol.attributes.urls.update_protocol_authors_url">
|
||||
<InlineEdit
|
||||
:value="protocol.attributes.authors"
|
||||
|
@ -68,15 +69,16 @@
|
|||
:allowBlank="true"
|
||||
:attributeName="`${i18n.t('Protocol')} ${i18n.t('protocols.header.authors_list')}`"
|
||||
:characterLimit="10000"
|
||||
:dataE2e="'protocolTemplates-protocolDetails-authors'"
|
||||
@update="updateAuthors"
|
||||
/>
|
||||
</span>
|
||||
<span class="authors-list" v-else>
|
||||
<span class="authors-list" data-e2e="e2e-TX-protocolTemplates-protocolDetails-authorsPublished" v-else>
|
||||
{{ protocol.attributes.authors }}
|
||||
</span>
|
||||
</p>
|
||||
<p class="data-block keywords-data">
|
||||
<span>{{ i18n.t("protocols.header.keywords") }}</span>
|
||||
<span data-e2e="e2e-TX-protocolTemplates-protocolDetails-keywordsLabel">{{ i18n.t("protocols.header.keywords") }}</span>
|
||||
<span
|
||||
class="keywords-list"
|
||||
v-if="protocol.attributes.urls.update_protocol_authors_url || protocol.attributes.keywords.length">
|
||||
|
@ -90,6 +92,7 @@
|
|||
:noEmptyOption="false"
|
||||
:selectAppearance="'tag'"
|
||||
:viewMode="protocol.attributes.urls.update_protocol_keywords_url == null"
|
||||
:dataE2e="'protocolTemplates-protocolDetails-keywords'"
|
||||
@dropdown:changed="updateKeywords"
|
||||
/>
|
||||
</span>
|
||||
|
@ -117,7 +120,7 @@ export default {
|
|||
protocol: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
@dragover.prevent
|
||||
:data-id="step.id"
|
||||
:class="{ 'draging-file': dragingFile, 'editing-name': editingName, 'locked': !urls.update_url, 'pointer-events-none': addingContent }"
|
||||
:data-e2e="`e2e-CO-protocol-step${step.id}`"
|
||||
>
|
||||
<div class="drop-message" @dragleave.prevent="!showFileModal ? dragingFile = false : null">
|
||||
{{ i18n.t('protocols.steps.drop_message', { position: step.attributes.position + 1 }) }}
|
||||
|
@ -18,7 +19,8 @@
|
|||
:href="'#stepBody' + step.id"
|
||||
data-toggle="collapse"
|
||||
data-remote="true"
|
||||
@click="toggleCollapsed">
|
||||
@click="toggleCollapsed"
|
||||
:data-e2e="`e2e-BT-protocol-step${step.id}-toggleCollapsed`">
|
||||
<span class="sn-icon sn-icon-right "></span>
|
||||
</a>
|
||||
<div v-if="!inRepository" class="step-complete-container" :class="{ 'step-element--locked': !urls.state_url }">
|
||||
|
@ -27,9 +29,10 @@
|
|||
@keyup.enter="changeState"
|
||||
tabindex="0"
|
||||
:title="step.attributes.completed ? i18n.t('protocols.steps.status.uncomplete') : i18n.t('protocols.steps.status.complete')"
|
||||
:data-e2e="`e2e-BT-protocol-step${step.id}-toggleCompleted`"
|
||||
></div>
|
||||
</div>
|
||||
<div class="step-position leading-5">
|
||||
<div class="step-position leading-5" :data-e2e="`e2e-TX-protocol-step${step.id}-position`">
|
||||
{{ step.attributes.position + 1 }}.
|
||||
</div>
|
||||
</div>
|
||||
|
@ -48,6 +51,7 @@
|
|||
@editingEnabled="editingName = true"
|
||||
@editingDisabled="editingName = false"
|
||||
:editOnload="step.newStep == true"
|
||||
:dataE2e="`protocol-step${step.id}-title`"
|
||||
@update="updateName"
|
||||
/>
|
||||
</div>
|
||||
|
@ -60,6 +64,7 @@
|
|||
:btnText="i18n.t('protocols.steps.insert.button')"
|
||||
:position="'right'"
|
||||
:caret="true"
|
||||
:dataE2e="`e2e-DD-protocol-step${step.id}-insertContent`"
|
||||
@create:table="(...args) => this.createElement('table', ...args)"
|
||||
@create:checklist="createElement('checklist')"
|
||||
@create:text="createElement('text')"
|
||||
|
@ -98,6 +103,7 @@
|
|||
:btnClasses="'btn btn-light icon-btn'"
|
||||
:position="'right'"
|
||||
:btnIcon="'sn-icon sn-icon-more-hori'"
|
||||
:dataE2e="`e2e-DD-protocol-step${step.id}-options`"
|
||||
@reorder="openReorderModal"
|
||||
@duplicate="duplicateStep"
|
||||
@delete="showDeleteModal"
|
||||
|
@ -116,6 +122,7 @@
|
|||
:reorderElementUrl="elements.length > 1 ? urls.reorder_elements_url : ''"
|
||||
:assignableMyModuleId="assignableMyModuleId"
|
||||
:isNew="element.isNew"
|
||||
:dataE2e="`protocol-step${step.id}`"
|
||||
@component:adding-content="($event) => addingContent = $event"
|
||||
@component:delete="deleteElement"
|
||||
@update="updateElement"
|
||||
|
@ -127,6 +134,7 @@
|
|||
:parent="step"
|
||||
:attachments="attachments"
|
||||
:attachmentsReady="attachmentsReady"
|
||||
:dataE2e="`protocol-step${step.id}`"
|
||||
@attachments:openFileModal="showFileModal = true"
|
||||
@attachment:deleted="attachmentDeleted"
|
||||
@attachment:update="updateAttachment"
|
||||
|
@ -142,6 +150,7 @@
|
|||
<ReorderableItemsModal v-if="reordering"
|
||||
:title="i18n.t('protocols.steps.modals.reorder_elements.title', { step_position: step.attributes.position + 1 })"
|
||||
:items="reorderableElements"
|
||||
:dataE2e="`e2e-BT-protocol-step${step.id}-reorder`"
|
||||
@reorder="updateElementOrder"
|
||||
@close="closeReorderModal"
|
||||
/>
|
||||
|
@ -215,13 +224,34 @@
|
|||
editingName: false,
|
||||
inlineEditError: null,
|
||||
wellPlateOptions: [
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.32_x_48'), emit: 'create:table', params: [32, 48] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.16_x_24'), emit: 'create:table', params: [16, 24] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.8_x_12'), emit: 'create:table', params: [8, 12] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.6_x_8'), emit: 'create:table', params: [6, 8] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.4_x_6'), emit: 'create:table', params: [4, 6] },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.3_x_4'), emit: 'create:table', params: [3, 4]},
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.2_x_3'), emit: 'create:table', params: [2, 3] }
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.32_x_48'),
|
||||
emit: 'create:table',
|
||||
params: [32, 48],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-32` },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.16_x_24'),
|
||||
emit: 'create:table',
|
||||
params: [16, 24],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-16` },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.8_x_12'),
|
||||
emit: 'create:table',
|
||||
params: [8, 12],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-8` },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.6_x_8'),
|
||||
emit: 'create:table',
|
||||
params: [6, 8],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-6` },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.4_x_6'),
|
||||
emit: 'create:table',
|
||||
params: [4, 6],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-4` },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.3_x_4'),
|
||||
emit: 'create:table',
|
||||
params: [3, 4],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-3` },
|
||||
{ text: I18n.t('protocols.steps.insert.well_plate_options.2_x_3'),
|
||||
emit: 'create:table',
|
||||
params: [2, 3],
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellPlate-2` }
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -289,25 +319,29 @@
|
|||
if (this.urls.upload_attachment_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('protocols.steps.insert.add_file'),
|
||||
emit: 'create:file'
|
||||
emit: 'create:file',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertAttachment-file`
|
||||
}]);
|
||||
}
|
||||
if (this.step.attributes.wopi_enabled) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('assets.create_wopi_file.button_text'),
|
||||
emit: 'create:wopi_file'
|
||||
emit: 'create:wopi_file',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertAttachment-wopi`
|
||||
}]);
|
||||
}
|
||||
if (this.step.attributes.open_vector_editor_context.new_sequence_asset_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('open_vector_editor.new_sequence_file'),
|
||||
emit: 'create:ove_file'
|
||||
emit: 'create:ove_file',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertAttachment-sequence`
|
||||
}]);
|
||||
}
|
||||
if (this.step.attributes.marvinjs_enabled) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('marvinjs.new_button'),
|
||||
emit: 'create:marvinjs_file'
|
||||
emit: 'create:marvinjs_file',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertAttachment-chemicalDrawing`
|
||||
}]);
|
||||
}
|
||||
return menu;
|
||||
|
@ -317,21 +351,26 @@
|
|||
if (this.urls.update_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('protocols.steps.insert.text'),
|
||||
emit: 'create:text'
|
||||
emit: 'create:text',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertText`
|
||||
},{
|
||||
text: this.i18n.t('protocols.steps.insert.attachment'),
|
||||
submenu: this.filesMenu,
|
||||
position: 'left'
|
||||
position: 'left',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertAttachment`
|
||||
},{
|
||||
text: this.i18n.t('protocols.steps.insert.table'),
|
||||
emit: 'create:table'
|
||||
emit: 'create:table',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertTable`
|
||||
},{
|
||||
text: this.i18n.t('protocols.steps.insert.well_plate'),
|
||||
submenu: this.wellPlateOptions,
|
||||
position: 'left'
|
||||
position: 'left',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertWellplate`
|
||||
},{
|
||||
text: this.i18n.t('protocols.steps.insert.checklist'),
|
||||
emit: 'create:checklist'
|
||||
emit: 'create:checklist',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-insertChecklist`
|
||||
}]);
|
||||
}
|
||||
|
||||
|
@ -342,19 +381,22 @@
|
|||
if (this.urls.reorder_elements_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('protocols.steps.options_dropdown.rearrange'),
|
||||
emit: 'reorder'
|
||||
emit: 'reorder',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-stepOptions-rearrange`
|
||||
}]);
|
||||
}
|
||||
if (this.urls.duplicate_step_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('protocols.steps.options_dropdown.duplicate'),
|
||||
emit: 'duplicate'
|
||||
emit: 'duplicate',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-stepOptions-duplicate`
|
||||
}]);
|
||||
}
|
||||
if (this.urls.delete_url) {
|
||||
menu = menu.concat([{
|
||||
text: this.i18n.t('protocols.steps.options_dropdown.delete'),
|
||||
emit: 'delete'
|
||||
emit: 'delete',
|
||||
data_e2e: `e2e-BT-protocol-step${this.step.id}-stepOptions-delete`
|
||||
}]);
|
||||
}
|
||||
return menu;
|
||||
|
|
|
@ -2,31 +2,32 @@
|
|||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form @submit.prevent="submit">
|
||||
<div class="modal-content">
|
||||
<div class="modal-content" data-e2e="e2e-MD-newProtocol">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-e2e="e2e-BT-newProtocolModal-close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label">
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label" data-e2e="e2e-TX-newProtocolModal-title">
|
||||
{{ i18n.t("protocols.new_protocol_modal.title_new") }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="mb-6">
|
||||
<label class="sci-label">{{ i18n.t("protocols.new_protocol_modal.name_label") }}</label>
|
||||
<label class="sci-label" data-e2e="e2e-TX-newProtocolModal-inputLabel">{{ i18n.t("protocols.new_protocol_modal.name_label") }}</label>
|
||||
<div class="sci-input-container-v2" :class="{'error': error}" :data-error="error">
|
||||
<input type="text" v-model="name"
|
||||
class="sci-input-field"
|
||||
autofocus="true" ref="input"
|
||||
data-e2e="e2e-IF-newProtocolModal-name"
|
||||
:placeholder="i18n.t('protocols.new_protocol_modal.name_placeholder')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 text-xs items-center">
|
||||
<div class="sci-checkbox-container">
|
||||
<input type="checkbox" class="sci-checkbox" v-model="visible" value="visible"/>
|
||||
<input type="checkbox" class="sci-checkbox" v-model="visible" value="visible" data-e2e="e2e-CB-newProtocolModal-grantAccess"/>
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
<span v-html="i18n.t('protocols.new_protocol_modal.access_label')"></span>
|
||||
<span v-html="i18n.t('protocols.new_protocol_modal.access_label')" data-e2e="e2e-TX-newProtocolModal-grantAccess"></span>
|
||||
</div>
|
||||
<div class="mt-6" :class="{'hidden': !visible}">
|
||||
<label class="sci-label">{{ i18n.t("protocols.new_protocol_modal.role_label") }}</label>
|
||||
|
@ -34,8 +35,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" type="submit" :disabled="visible && !defaultRole">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal" data-e2e="e2e-BT-newProtocolModal-cancel">{{ i18n.t('general.cancel') }}</button>
|
||||
<button class="btn btn-primary" type="submit" :disabled="visible && !defaultRole" data-e2e="e2e-BT-newProtocolModal-create">
|
||||
{{ i18n.t('protocols.new_protocol_modal.create_new') }}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -203,7 +203,8 @@ export default {
|
|||
menuItems: [
|
||||
{
|
||||
emit: 'import_file',
|
||||
text: this.i18n.t('protocols.index.import_eln')
|
||||
text: this.i18n.t('protocols.index.import_eln'),
|
||||
data_e2e: 'e2e-BT-topToolbar-importEln'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -214,13 +215,15 @@ export default {
|
|||
text: `<span>${this.i18n.t('protocols.index.import_docx')}</span>
|
||||
<span class="bg-sn-coral text-sn-white text-[8px] absolute leading-none p-1 top-px rounded-[1px] right-px">
|
||||
${this.i18n.t('protocols.index.beta')}
|
||||
</span>`
|
||||
</span>`,
|
||||
data_e2e: 'e2e-BT-topToolbar-importDocx'
|
||||
});
|
||||
}
|
||||
|
||||
importMenu.menuItems.push({
|
||||
emit: 'import_protocols_io',
|
||||
text: this.i18n.t('protocols.index.import_protocols_io')
|
||||
text: this.i18n.t('protocols.index.import_protocols_io'),
|
||||
data_e2e: 'e2e-BT-topToolbar-importProtocolsIo'
|
||||
});
|
||||
|
||||
left.push(importMenu);
|
||||
|
|
|
@ -1,33 +1,36 @@
|
|||
<template>
|
||||
<div v-if="modalOpened">
|
||||
<component
|
||||
v-if="activeStep !== 'ExportModal'"
|
||||
:is="activeStep"
|
||||
:params="params"
|
||||
:uploading="uploading"
|
||||
@uploadFile="uploadFile"
|
||||
@generatePreview="generatePreview"
|
||||
@changeStep="changeStep"
|
||||
@importRows="importRecords"
|
||||
/>
|
||||
<ExportModal
|
||||
v-else
|
||||
:rows="[{id: params.id, team: params.attributes.team_name}]"
|
||||
:exportAction="params.attributes.export_actions"
|
||||
@close="activeStep ='UploadStep'"
|
||||
@export="activeStep = 'UploadStep'"
|
||||
/>
|
||||
<div v-if="modalOpened" class="relative">
|
||||
<component
|
||||
v-if="activeStep !== 'ExportModal'"
|
||||
:is="activeStep"
|
||||
:params="params"
|
||||
:key="modalId"
|
||||
:uploading="uploading"
|
||||
:loading="loading"
|
||||
@uploadFile="uploadFile"
|
||||
@generatePreview="generatePreview"
|
||||
@changeStep="changeStep"
|
||||
@importRows="importRecords"
|
||||
|
||||
/>
|
||||
<ExportModal
|
||||
v-else
|
||||
:rows="[{id: params.id, team: params.attributes.team_name}]"
|
||||
:exportAction="params.attributes.export_actions"
|
||||
@close="activeStep ='UploadStep'"
|
||||
@export="activeStep = 'UploadStep'"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/* global HelperModule */
|
||||
|
||||
import axios from '../../../../packs/custom_axios';
|
||||
import InfoModal from '../../../shared/info_modal.vue';
|
||||
import UploadStep from './upload_step.vue';
|
||||
import MappingStep from './mapping_step.vue';
|
||||
import PreviewStep from './preview_step.vue';
|
||||
import SuccessStep from './success_step.vue';
|
||||
import ExportModal from '../export.vue';
|
||||
|
||||
export default {
|
||||
|
@ -37,7 +40,6 @@ export default {
|
|||
UploadStep,
|
||||
MappingStep,
|
||||
PreviewStep,
|
||||
SuccessStep,
|
||||
ExportModal
|
||||
},
|
||||
props: {
|
||||
|
@ -49,7 +51,9 @@ export default {
|
|||
modalOpened: false,
|
||||
activeStep: 'UploadStep',
|
||||
uploading: false,
|
||||
params: {}
|
||||
params: {},
|
||||
modalId: null,
|
||||
loading: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
@ -64,6 +68,7 @@ export default {
|
|||
axios.get(this.repositoryUrl)
|
||||
.then((response) => {
|
||||
this.params = response.data.data;
|
||||
this.modalId = Math.random().toString(36);
|
||||
this.modalOpened = true;
|
||||
});
|
||||
},
|
||||
|
@ -94,6 +99,10 @@ export default {
|
|||
this.activeStep = step;
|
||||
},
|
||||
importRecords(preview) {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
const jsonData = {
|
||||
file_id: this.params.temp_file.id,
|
||||
mappings: this.params.mapping,
|
||||
|
@ -102,6 +111,9 @@ export default {
|
|||
should_overwrite_with_empty_cells: this.params.updateWithEmptyCells,
|
||||
can_edit_existing_items: !this.params.onlyAddNewItems
|
||||
};
|
||||
|
||||
this.loading = true;
|
||||
|
||||
axios.post(this.params.attributes.urls.import_records, jsonData)
|
||||
.then((response) => {
|
||||
if (preview) {
|
||||
|
@ -109,8 +121,17 @@ export default {
|
|||
this.params.import_date = response.data.import_date;
|
||||
this.activeStep = 'PreviewStep';
|
||||
} else {
|
||||
this.activeStep = 'SuccessStep';
|
||||
HelperModule.flashAlertMsg(response.data.message, 'success');
|
||||
this.modalOpened = false;
|
||||
this.activeStep = null;
|
||||
$('.dataTable.repository-dataTable').DataTable().ajax.reload(null, false);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
})
|
||||
.catch((error) => {
|
||||
HelperModule.flashAlertMsg(error.response.data.message, 'danger');
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<Loading v-if="loading" />
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-e2e="e2e-BT-newInventoryModal-close">
|
||||
|
@ -11,18 +12,20 @@
|
|||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<p class="text-sn-dark-grey">
|
||||
{{ this.i18n.t('repositories.import_records.steps.step2.subtitle') }}
|
||||
</p>
|
||||
<div class="flex gap-6 items-center my-6">
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="flex items-center gap-2" :title="i18n.t('repositories.import_records.steps.step2.autoMappingTooltip')">
|
||||
<div class="sci-checkbox-container">
|
||||
<input type="checkbox" class="sci-checkbox" v-model="autoMapping" />
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
{{ i18n.t('repositories.import_records.steps.step2.autoMappingText') }}
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<!--
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="sci-checkbox-container my-auto">
|
||||
<input type="checkbox" class="sci-checkbox" :checked="updateWithEmptyCells" @change="toggleUpdateWithEmptyCells"/>
|
||||
<span class="sci-checkbox-label"></span>
|
||||
|
@ -36,13 +39,14 @@
|
|||
</div>
|
||||
{{ i18n.t('repositories.import_records.steps.step2.onlyAddNewItemsText') }}
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
{{ i18n.t('repositories.import_records.steps.step2.importedFileText') }} {{ params.file_name }}
|
||||
<hr class="m-0 mt-6">
|
||||
<div class="grid grid-cols-[3rem_auto_1.5rem_auto_5rem_auto] px-2">
|
||||
|
||||
<div v-for="(column, key) in columnLabels" class="flex items-center px-2 py-2">{{ column }}</div>
|
||||
<div v-for="(column, key) in columnLabels" class="flex items-center px-2 py-2 font-bold">{{ column }}</div>
|
||||
|
||||
<template v-for="(item, index) in params.import_data.header" :key="item">
|
||||
<MappingStepTableRow
|
||||
|
@ -50,7 +54,7 @@
|
|||
:item="item"
|
||||
:dropdownOptions="computedDropdownOptions"
|
||||
:params="params"
|
||||
:selected="this.selectedItemsIndexes.includes(index)"
|
||||
:value="this.selectedItems.find((item) => item.index === index)"
|
||||
@selection:changed="handleChange"
|
||||
:autoMapping="this.autoMapping"
|
||||
/>
|
||||
|
@ -76,7 +80,7 @@
|
|||
<button class="btn btn-secondary ml-auto" @click="close" aria-label="Close">
|
||||
{{ i18n.t('repositories.import_records.steps.step2.cancelBtnText') }}
|
||||
</button>
|
||||
<button class="btn btn-primary" :disabled="!rowsIsValid" @click="importRecords">
|
||||
<button class="btn btn-primary" @click="importRecords">
|
||||
{{ i18n.t('repositories.import_records.steps.step2.confirmBtnText') }}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -90,6 +94,7 @@ import axios from '../../../../packs/custom_axios';
|
|||
import SelectDropdown from '../../../shared/select_dropdown.vue';
|
||||
import MappingStepTableRow from './mapping_step_table_row.vue';
|
||||
import modalMixin from '../../../shared/modal_mixin';
|
||||
import Loading from '../../../shared/loading.vue';
|
||||
|
||||
export default {
|
||||
name: 'MappingStep',
|
||||
|
@ -97,17 +102,22 @@ export default {
|
|||
mixins: [modalMixin],
|
||||
components: {
|
||||
SelectDropdown,
|
||||
MappingStepTableRow
|
||||
MappingStepTableRow,
|
||||
Loading
|
||||
},
|
||||
props: {
|
||||
params: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
autoMapping: true,
|
||||
autoMapping: false,
|
||||
updateWithEmptyCells: false,
|
||||
onlyAddNewItems: false,
|
||||
columnLabels: {
|
||||
|
@ -119,7 +129,6 @@ export default {
|
|||
5: this.i18n.t('repositories.import_records.steps.step2.table.columnLabels.exampleData')
|
||||
},
|
||||
selectedItems: [],
|
||||
selectedItemsIndexes: [],
|
||||
importRecordsUrl: null,
|
||||
teamId: null,
|
||||
repositoryId: null,
|
||||
|
@ -130,131 +139,77 @@ export default {
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
toggleUpdateWithEmptyCells() {
|
||||
this.updateWithEmptyCells = !this.updateWithEmptyCells;
|
||||
},
|
||||
toggleOnlyAddNewItems() {
|
||||
this.onlyAddNewItems = !this.onlyAddNewItems;
|
||||
},
|
||||
handleChange(payload) {
|
||||
this.error = null;
|
||||
const { index, key, value } = payload;
|
||||
|
||||
// checking if the mapping is already selected
|
||||
const foundItem = this.selectedItems.find((item) => item.index === index);
|
||||
const item = this.selectedItems.find((i) => i.index === index);
|
||||
const usedBeforeItem = this.selectedItems.find((i) => i.key === key && i.index !== index);
|
||||
|
||||
// if it's not, add it
|
||||
if (!foundItem && key) {
|
||||
this.selectedItems = [...this.selectedItems, { index, key, value }];
|
||||
this.selectedItemsIndexes.push(index);
|
||||
}
|
||||
// if it is but the key is null then clear it
|
||||
if (foundItem && !key) {
|
||||
const indexToRemoveObj = this.selectedItems.findIndex((item) => item.index === index);
|
||||
const indexToRemoveStr = this.selectedItemsIndexes.indexOf(index);
|
||||
if ((indexToRemoveObj !== -1) && (indexToRemoveStr !== -1)) {
|
||||
this.selectedItems.splice(indexToRemoveObj, 1);
|
||||
this.selectedItemsIndexes.splice(indexToRemoveStr, 1);
|
||||
}
|
||||
}
|
||||
// if it is and the key is not null then update it
|
||||
if (foundItem && key) {
|
||||
const indexToRemoveObj = this.selectedItems.findIndex((item) => item.index === index);
|
||||
this.selectedItems.splice(indexToRemoveObj, 1);
|
||||
this.selectedItems = [...this.selectedItems, { index, key, value }];
|
||||
if (usedBeforeItem) {
|
||||
usedBeforeItem.key = null;
|
||||
usedBeforeItem.value = null;
|
||||
}
|
||||
|
||||
this.updateAvailableItemsStatus();
|
||||
},
|
||||
// necessary for tracking which options are already selected
|
||||
updateAvailableItemsStatus() {
|
||||
let updatedAvailableFields = [];
|
||||
const selectedItemsKeys = new Set(this.selectedItems.map((item) => item.key));
|
||||
|
||||
this.alwaysAvailableFields.forEach((field) => {
|
||||
if (selectedItemsKeys.has(field.key)) {
|
||||
const tempObj = { key: field.key, value: field.value, alreadySelected: true };
|
||||
updatedAvailableFields.push(tempObj);
|
||||
} else {
|
||||
updatedAvailableFields.push(field);
|
||||
}
|
||||
});
|
||||
|
||||
this.availableFields = updatedAvailableFields;
|
||||
updatedAvailableFields = [];
|
||||
item.key = key;
|
||||
item.value = value;
|
||||
},
|
||||
generateMapping() {
|
||||
const mapping = {};
|
||||
for (let i = 0; i < this.params.import_data.header.length; i++) {
|
||||
const foundItem = this.selectedItems.find((item) => item.index === i);
|
||||
if (foundItem) {
|
||||
mapping[foundItem.index] = (foundItem.key === 'new' ? foundItem.value : foundItem.key);
|
||||
} else {
|
||||
mapping[i] = '';
|
||||
}
|
||||
}
|
||||
return mapping;
|
||||
return this.selectedItems.reduce((obj, item) => {
|
||||
obj[item.index] = item.key || '';
|
||||
return obj;
|
||||
}, {});
|
||||
},
|
||||
importRecords() {
|
||||
const selectedItemsKeys = new Set(this.selectedItems.map((item) => item.key));
|
||||
if (!selectedItemsKeys.has('-1')) {
|
||||
if (!this.selectedItems.find((item) => item.key === '-1')) {
|
||||
this.error = this.i18n.t('repositories.import_records.steps.step2.selectNamePropertyError');
|
||||
return '';
|
||||
}
|
||||
|
||||
this.$emit(
|
||||
'generatePreview',
|
||||
this.generateMapping(),
|
||||
this.updateWithEmptyCells,
|
||||
this.onlyAddNewItems
|
||||
this.generateMapping()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
computedDropdownOptions() {
|
||||
const columnKeyToLabelMapping = {};
|
||||
columnKeyToLabelMapping[-1] = this.i18n.t('repositories.import_records.steps.step2.computedDropdownOptions.name');
|
||||
|
||||
if (this.repositoryColumns) {
|
||||
this.repositoryColumns.forEach((el) => {
|
||||
const [key, colName, colType] = el;
|
||||
columnKeyToLabelMapping[key] = this.i18n.t(`repositories.import_records.steps.step2.computedDropdownOptions.${colType}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.availableFields) {
|
||||
let options = this.availableFields.map((el) => [String(el.key), `${String(el.value)} (${columnKeyToLabelMapping[el.key]})`]);
|
||||
options = [['new', this.i18n.t('repositories.import_records.steps.step2.table.tableRow.importAsNewColumn')]].concat(options);
|
||||
return options;
|
||||
}
|
||||
return [];
|
||||
return this.availableFields
|
||||
.map((el) => [String(el.key), `${String(el.value)} (${el.typeName})`]);
|
||||
// options = [['new', this.i18n.t('repositories.import_records.steps.step2.table.tableRow.importAsNewColumn')]].concat(options);
|
||||
},
|
||||
computedImportedIgnoredInfo() {
|
||||
const importedSum = this.selectedItems.length;
|
||||
const ignoredSum = this.params.import_data.header.length - importedSum;
|
||||
const importedSum = this.selectedItems.filter((i) => i.key).length;
|
||||
const ignoredSum = this.selectedItems.length - importedSum;
|
||||
return { importedSum, ignoredSum };
|
||||
},
|
||||
rowsIsValid() {
|
||||
let valid = true;
|
||||
this.selectedItems.forEach((v) => {
|
||||
if (v.key === 'new' && (!v.value.type || v.value.name.length < 2)) {
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
return valid;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.repositoryColumns = this.params.attributes.repository_columns;
|
||||
|
||||
// Adding alreadySelected attribute for tracking
|
||||
const tempAvailableFields = [];
|
||||
this.availableFields = [];
|
||||
|
||||
this.selectedItems = this.params.import_data.header.map((item, index) => { return { index, key: null, value: null }; });
|
||||
|
||||
Object.entries(this.params.import_data.available_fields).forEach(([key, value]) => {
|
||||
const field = { key, value, alreadySelected: false };
|
||||
tempAvailableFields.push(field);
|
||||
let columnTypeName = '';
|
||||
if (key === '-1') {
|
||||
columnTypeName = this.i18n.t('repositories.import_records.steps.step2.computedDropdownOptions.name');
|
||||
} else if (key === '0') {
|
||||
columnTypeName = this.i18n.t('repositories.import_records.steps.step2.computedDropdownOptions.id');
|
||||
} else {
|
||||
const column = this.repositoryColumns.find((el) => el[0] === parseInt(key, 10));
|
||||
columnTypeName = this.i18n.t(`repositories.import_records.steps.step2.computedDropdownOptions.${column[2]}`);
|
||||
}
|
||||
const field = {
|
||||
key, value, alreadySelected: false, typeName: columnTypeName
|
||||
};
|
||||
this.availableFields.push(field);
|
||||
});
|
||||
this.availableFields = tempAvailableFields;
|
||||
this.alwaysAvailableFields = tempAvailableFields;
|
||||
},
|
||||
mounted() {
|
||||
this.autoMapping = true;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -17,25 +17,21 @@
|
|||
<div class="py-1 min-h-12 flex items-center flex-col gap-2 px-2" :class="{
|
||||
'bg-sn-super-light-blue': selected
|
||||
}">
|
||||
|
||||
<!-- system generated data -->
|
||||
<SelectDropdown v-if="systemGeneratedData.includes(item)"
|
||||
:disabled="true"
|
||||
:placeholder="String(item)"
|
||||
></SelectDropdown>
|
||||
<SelectDropdown
|
||||
v-else
|
||||
:options="dropdownOptions"
|
||||
@change="changeSelected"
|
||||
:clearable="true"
|
||||
:size="'sm'"
|
||||
:class="{
|
||||
'outline-sn-alert-brittlebush outline-1 outline rounded': matchNotFound
|
||||
}"
|
||||
:placeholder="computeMatchNotFound ?
|
||||
i18n.t('repositories.import_records.steps.step2.table.tableRow.placeholders.matchNotFound') :
|
||||
i18n.t('repositories.import_records.steps.step2.table.tableRow.placeholders.doNotImport')"
|
||||
:title="this.selectedColumnType?.value"
|
||||
:value="this.selectedColumnType?.key"
|
||||
></SelectDropdown>
|
||||
<template v-if="selectedColumnType?.key == 'new'">
|
||||
<template v-if="false">
|
||||
<SelectDropdown
|
||||
:options="newColumnTypes"
|
||||
@change="(v) => { newColumn.type = v }"
|
||||
|
@ -52,31 +48,12 @@
|
|||
<div class="py-1 min-h-12 px-2 flex items-center" :class="{
|
||||
'bg-sn-super-light-blue': selected
|
||||
}">
|
||||
<!-- import -->
|
||||
<i v-if="this.selectedColumnType?.key && this.selectedColumnType?.value === item && !systemGeneratedData.includes(item)"
|
||||
class="sn-icon sn-icon-check" :title="i18n.t('repositories.import_records.steps.step2.table.tableRow.importedColumnTitle')">
|
||||
</i>
|
||||
|
||||
<!-- default column -->
|
||||
<i v-else-if="systemGeneratedData.includes(item)"
|
||||
class="sn-icon sn-icon-check text-sn-sleepy-grey" :title="i18n.t('repositories.import_records.steps.step2.table.tableRow.defaultColumnTitle')">
|
||||
</i>
|
||||
|
||||
<!-- user defined this column -->
|
||||
<i v-else-if="this.selectedColumnType?.key && this.selectedColumnType?.value !== item"
|
||||
class="sn-icon sn-icon-info text-sn-science-blue"
|
||||
:title="`${i18n.t('repositories.import_records.steps.step2.table.tableRow.userDefinedColumnTitle')} ${this.selectedColumnType.value}`"></i>
|
||||
|
||||
<!-- error: can not import -->
|
||||
<!-- <i v-else-if=""></i> -->
|
||||
|
||||
<!-- match not found -->
|
||||
<i v-else-if="computeMatchNotFound"
|
||||
class="sn-icon sn-icon-close text-sn-alert-brittlebush" :title="i18n.t('repositories.import_records.steps.step2.table.tableRow.matchNotFoundColumnTitle')">
|
||||
</i>
|
||||
|
||||
<!-- do not import -->
|
||||
<i v-else class="sn-icon sn-icon-close text-sn-sleepy-grey" :title="i18n.t('repositories.import_records.steps.step2.table.tableRow.doNotImportColumnTitle')"></i>
|
||||
<i v-if="differentMapingName" :title="i18n.t('repositories.import_records.steps.step2.table.tableRow.importedColumnTitle')"
|
||||
class="sn-icon sn-icon-info text-sn-science-blue"></i>
|
||||
<i v-else-if="columnMapped" :title="i18n.t('repositories.import_records.steps.step2.table.tableRow.importedColumnTitle')" class="sn-icon sn-icon-check"></i>
|
||||
<i v-else-if="matchNotFound" :title="i18n.t('repositories.import_records.steps.step2.table.tableRow.matchNotFoundColumnTitle')"
|
||||
class="sn-icon sn-icon-close text-sn-alert-brittlebush"></i>
|
||||
<i v-else :title="i18n.t('repositories.import_records.steps.step2.table.tableRow.doNotImportColumnTitle')" class="sn-icon sn-icon-close text-sn-sleepy-grey"></i>
|
||||
</div>
|
||||
|
||||
<div class="py-1 min-h-12 px-2 flex items-center" :title="params.import_data.columns[index]" :class="{
|
||||
|
@ -114,10 +91,7 @@ export default {
|
|||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
selected: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
value: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -129,24 +103,16 @@ export default {
|
|||
newColumnTypes: [
|
||||
['Text', this.i18n.t('repositories.import_records.steps.step2.table.tableRow.newColumnType.text')],
|
||||
['List', this.i18n.t('repositories.import_records.steps.step2.table.tableRow.newColumnType.list')]
|
||||
],
|
||||
systemGeneratedData: [
|
||||
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.itemId'),
|
||||
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.createdOn'),
|
||||
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.addedBy'),
|
||||
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.addedOn'),
|
||||
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.archivedBy'),
|
||||
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.archivedOn'),
|
||||
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.updatedBy'),
|
||||
this.i18n.t('repositories.import_records.steps.step2.table.tableRow.systemGeneratedData.updatedOn')]
|
||||
]
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
newColumn() {
|
||||
this.selectedColumnType.value = this.newColumn;
|
||||
this.$emit('selection:changed', this.selectedColumnType);
|
||||
selected() {
|
||||
if (this.value?.key === null) {
|
||||
this.selectedColumnType = null;
|
||||
}
|
||||
},
|
||||
autoMapping(newVal, oldVal) {
|
||||
autoMapping(newVal) {
|
||||
if (newVal === true) {
|
||||
this.autoMap();
|
||||
} else {
|
||||
|
@ -157,6 +123,18 @@ export default {
|
|||
computed: {
|
||||
computeMatchNotFound() {
|
||||
return this.autoMapping && ((this.selectedColumnType && !this.selectedColumnType.key) || !this.selectedColumnType);
|
||||
},
|
||||
selected() {
|
||||
return !!this.value?.key;
|
||||
},
|
||||
differentMapingName() {
|
||||
return this.columnMapped && this.selectedColumnType?.value !== this.item;
|
||||
},
|
||||
matchNotFound() {
|
||||
return this.autoMapping && !this.selectedColumnType?.key;
|
||||
},
|
||||
columnMapped() {
|
||||
return this.selectedColumnType?.key;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -171,15 +149,9 @@ export default {
|
|||
this.changeSelected(null);
|
||||
},
|
||||
changeSelected(e) {
|
||||
let value;
|
||||
if (e === 'new') {
|
||||
value = this.newColumn;
|
||||
} else {
|
||||
value = this.params.import_data.available_fields[e];
|
||||
}
|
||||
const selectedColumnType = { index: this.index, key: e, value };
|
||||
this.selectedColumnType = selectedColumnType;
|
||||
this.$emit('selection:changed', selectedColumnType);
|
||||
const value = this.params.import_data.available_fields[e];
|
||||
this.selectedColumnType = { index: this.index, key: e, value };
|
||||
this.$emit('selection:changed', this.selectedColumnType);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<Loading v-if="loading" />
|
||||
<div class="modal-content grow">
|
||||
<div class="modal-header gap-4">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-e2e="e2e-BT-newInventoryModal-close">
|
||||
|
@ -11,6 +12,7 @@
|
|||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<p class="text-sn-dark-grey mb-6">
|
||||
{{ i18n.t('repositories.import_records.steps.step3.subtitle', { inventory: params.attributes.name }) }}
|
||||
</p>
|
||||
|
@ -18,32 +20,32 @@
|
|||
<div>
|
||||
<div v-html="i18n.t('repositories.import_records.steps.step3.updated_items')"></div>
|
||||
<hr class="my-1">
|
||||
<h2 class="m-0 text-sn-alert-green">0</h2>
|
||||
<h2 class="m-0 text-sn-alert-green">{{ counters.updated }}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<div v-html="i18n.t('repositories.import_records.steps.step3.new_items')"></div>
|
||||
<hr class="my-1">
|
||||
<h2 class="m-0 text-sn-alert-green">0</h2>
|
||||
<h2 class="m-0 text-sn-alert-green">{{ counters.created }}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<div v-html="i18n.t('repositories.import_records.steps.step3.unchanged_items')"></div>
|
||||
<hr class="my-1">
|
||||
<h2 class="m-0 ">0</h2>
|
||||
<h2 class="m-0 ">{{ counters.unchanged }}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<div v-html="i18n.t('repositories.import_records.steps.step3.duplicated_items')"></div>
|
||||
<hr class="my-1">
|
||||
<h2 class="m-0 ">0</h2>
|
||||
<h2 class="m-0 text-sn-alert-passion">{{ counters.duplicated }}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<div v-html="i18n.t('repositories.import_records.steps.step3.invalid_items')"></div>
|
||||
<hr class="my-1">
|
||||
<h2 class="m-0 text-sn-alert-passion">0</h2>
|
||||
<h2 class="m-0 text-sn-alert-passion">{{ counters.invalid }}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<div v-html="i18n.t('repositories.import_records.steps.step3.invalid_items')"></div>
|
||||
<div v-html="i18n.t('repositories.import_records.steps.step3.archived_items')"></div>
|
||||
<hr class="my-1">
|
||||
<h2 class="m-0 text-sn-alert-passion">0</h2>
|
||||
<h2 class="m-0">{{ counters.archived }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="my-6">
|
||||
|
@ -67,7 +69,7 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" @click="$emit('changeStep', 'MappingStep')">
|
||||
{{ i18n.t('repositories.import_records.steps.step3.cancel') }}
|
||||
{{ i18n.t('general.back') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" @click="$emit('importRows')">
|
||||
{{ i18n.t('repositories.import_records.steps.step3.confirm') }}
|
||||
|
@ -81,7 +83,7 @@
|
|||
<script>
|
||||
import { AgGridVue } from 'ag-grid-vue3';
|
||||
import modalMixin from '../../../shared/modal_mixin';
|
||||
|
||||
import Loading from '../../../shared/loading.vue';
|
||||
|
||||
export default {
|
||||
name: 'PreviewStep',
|
||||
|
@ -90,16 +92,31 @@ export default {
|
|||
params: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AgGridVue
|
||||
AgGridVue,
|
||||
Loading
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
counters() {
|
||||
return {
|
||||
updated: this.filterRows('updated').length,
|
||||
created: this.filterRows('created').length,
|
||||
unchanged: this.filterRows('unchanged').length,
|
||||
duplicated: this.filterRows('duplicated').length,
|
||||
invalid: this.filterRows('invalid').length,
|
||||
archived: this.filterRows('archived').length
|
||||
};
|
||||
},
|
||||
columnDefs() {
|
||||
const columns = [
|
||||
{
|
||||
|
@ -120,8 +137,9 @@ export default {
|
|||
});
|
||||
|
||||
columns.push({
|
||||
field: 'status',
|
||||
field: 'import_status',
|
||||
headerName: this.i18n.t('repositories.import_records.steps.step3.status'),
|
||||
cellRenderer: this.statusRenderer,
|
||||
pinned: 'right'
|
||||
});
|
||||
|
||||
|
@ -142,6 +160,40 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
filterRows(status) {
|
||||
return this.params.preview.data.filter((r) => r.attributes.import_status === status);
|
||||
},
|
||||
statusRenderer(params) {
|
||||
const { import_status: importStatus, import_message: importMessage } = params.data;
|
||||
|
||||
let message = '';
|
||||
let color = '';
|
||||
let icon = '';
|
||||
|
||||
if (importStatus === 'created' || importStatus === 'updated') {
|
||||
message = this.i18n.t(`repositories.import_records.steps.step3.status_message.${importStatus}`);
|
||||
color = 'text-sn-alert-green';
|
||||
icon = 'check';
|
||||
} else if (importStatus === 'unchanged' || importStatus === 'archived') {
|
||||
message = this.i18n.t(`repositories.import_records.steps.step3.status_message.${importStatus}`);
|
||||
icon = 'hamburger';
|
||||
} else if (importStatus === 'duplicated' || importStatus === 'invalid') {
|
||||
message = this.i18n.t(`repositories.import_records.steps.step3.status_message.${importStatus}`);
|
||||
color = 'text-sn-alert-passion';
|
||||
icon = 'close';
|
||||
}
|
||||
|
||||
if (importMessage) {
|
||||
message = importMessage;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="flex items-center ${color} gap-2.5">
|
||||
<i class="sn-icon sn-icon-${icon} "></i>
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-sm" role="document">
|
||||
<div class="modal-content grow">
|
||||
<div class="modal-header gap-4">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-e2e="e2e-BT-newInventoryModal-close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label" data-e2e="e2e-TX-newInventoryModal-title">
|
||||
{{ i18n.t('repositories.import_records.steps.step4.title') }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="text-sn-dark-grey mb-6">
|
||||
{{ i18n.t('repositories.import_records.steps.step4.subtitle', { inventory: params.attributes.name }) }}
|
||||
</p>
|
||||
<div>
|
||||
<b>{{ i18n.t('repositories.import_records.steps.step4.import_date') }}</b>
|
||||
{{ params.import_date }}
|
||||
</div>
|
||||
<div>
|
||||
<b>{{ i18n.t('repositories.import_records.steps.step4.imported_file') }}</b>
|
||||
{{ params.file_name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" >
|
||||
{{ i18n.t('repositories.import_records.steps.step4.download_report') }}
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" @click="close">
|
||||
{{ i18n.t('repositories.import_records.steps.step4.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modalMixin from '../../../shared/modal_mixin';
|
||||
|
||||
|
||||
export default {
|
||||
name: 'SuccessStep',
|
||||
mixins: [modalMixin],
|
||||
props: {
|
||||
params: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog flex" role="document" :class="{'!w-[900px]': showingInfo}">
|
||||
<div v-if="showingInfo" class="w-[300px] h-full bg-sn-super-light-grey p-6 rounded-s text-sn-dark-grey">
|
||||
<div v-if="showingInfo" class="w-[300px] shrink-0 h-full bg-sn-super-light-grey p-6 rounded-s text-sn-dark-grey">
|
||||
<h3 class="my-0 mb-4">{{ this.i18n.t('repositories.import_records.info_sidebar.title') }}</h3>
|
||||
<div v-for="i in 4" :key="i" class="flex gap-3 mb-4">
|
||||
<span class="btn btn-secondary icon-btn !text-sn-black">
|
||||
<span class="btn btn-secondary icon-btn !text-sn-black !pointer-events-none">
|
||||
<i class="sn-icon"
|
||||
:class="i18n.t(`repositories.import_records.info_sidebar.elements.element${i - 1}.icon`)"
|
||||
></i>
|
||||
|
@ -15,10 +15,10 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="flex gap-3 mb-4 items-center">
|
||||
<span class="btn btn-secondary icon-btn !text-sn-black">
|
||||
<span class="btn btn-secondary icon-btn !text-sn-black !pointer-events-none">
|
||||
<i class="sn-icon sn-icon-open"></i>
|
||||
</span>
|
||||
<a :href="i18n.t('repositories.import_records.info_sidebar.elements.element4.linkTo')" class="font-bold">
|
||||
<a :href="i18n.t('repositories.import_records.info_sidebar.elements.element4.linkTo')" class="font-bold" target="_blank">
|
||||
{{ i18n.t('repositories.import_records.info_sidebar.elements.element4.label') }}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -85,13 +85,12 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import axios from '../../../../packs/custom_axios';
|
||||
import DragAndDropUpload from '../../../shared/drag_and_drop_upload.vue';
|
||||
import modalMixin from '../../../shared/modal_mixin';
|
||||
|
||||
export default {
|
||||
name: 'UploadStep',
|
||||
emits: ['uploadFile'],
|
||||
emits: ['uploadFile', 'close'],
|
||||
components: {
|
||||
DragAndDropUpload
|
||||
},
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<div class="content__attachments pr-8" :id='"content__attachments-" + parent.id'>
|
||||
<div class="content__attachments pr-8"
|
||||
:id='"content__attachments-" + parent.id'
|
||||
:data-e2e="`e2e-CO-${dataE2e}-attachments`">
|
||||
<div class="sci-divider my-6"></div>
|
||||
<div class="content__attachments-actions">
|
||||
<div class="title">
|
||||
|
@ -11,6 +13,7 @@
|
|||
:btnText="i18n.t('attachments.preview_menu')"
|
||||
:position="'right'"
|
||||
:caret="true"
|
||||
:data_e2e="`e2e-DD-${dataE2e}-attachments-viewOptions`"
|
||||
@attachment:viewMode = "changeAttachmentsViewMode"
|
||||
></MenuDropdown>
|
||||
<MenuDropdown
|
||||
|
@ -18,6 +21,7 @@
|
|||
:btnIcon="'sn-icon sn-icon-sort-down'"
|
||||
:btnClasses="'btn btn-light icon-btn'"
|
||||
:position="'right'"
|
||||
:data_e2e="`e2e-DD-${dataE2e}-attachments-orderOptions`"
|
||||
@attachment:order = "changeAttachmentsOrder"
|
||||
></MenuDropdown>
|
||||
</div>
|
||||
|
@ -29,6 +33,7 @@
|
|||
:is="attachment_view_mode(attachmentsOrdered[index])"
|
||||
:attachment="attachment"
|
||||
:parentId="parseInt(parent.id)"
|
||||
:dataE2e="`${dataE2e}`"
|
||||
@attachment:viewMode="updateAttachmentViewMode"
|
||||
@attachment:delete="deleteAttachment(attachment.id)"
|
||||
@attachment:moved="attachmentMoved"
|
||||
|
@ -64,6 +69,10 @@ export default {
|
|||
attachmentsReady: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -119,7 +128,8 @@ export default {
|
|||
active: this.parent.attributes.assets_view_mode == viewMode,
|
||||
text: this.i18n.t(`attachments.view_mode.${viewMode}_html`),
|
||||
emit: 'attachment:viewMode',
|
||||
params: viewMode
|
||||
params: viewMode,
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-viewOptions-${viewMode}`
|
||||
});
|
||||
});
|
||||
return menu;
|
||||
|
@ -131,7 +141,8 @@ export default {
|
|||
text: this.i18n.t(`general.sort_new.${orderOption}`),
|
||||
emit: 'attachment:order',
|
||||
params: orderOption,
|
||||
active: this.parent.attributes.assets_order === orderOption
|
||||
active: this.parent.attributes.assets_order === orderOption,
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-orderOptions-${orderOption}`
|
||||
});
|
||||
});
|
||||
return menu;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<div class="attachment-container asset" :data-asset-id="attachment.id">
|
||||
<div class="attachment-container asset"
|
||||
:data-asset-id="attachment.id"
|
||||
:data-e2e="`e2e-CO-${dataE2e}-attachment${attachment.id}-empty`">
|
||||
<div
|
||||
class="file-name"
|
||||
:id="`modal_link${attachment.id}`"
|
||||
|
@ -23,6 +25,10 @@ export default {
|
|||
parentId: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div
|
||||
class="inline-attachment-container asset"
|
||||
:class="[{'menu-dropdown-open': isMenuDropdownOpen}, {'context-menu-open': isContextMenuOpen }]"
|
||||
:data-e2e="`e2e-CO-${dataE2e}-attachment${attachment.id}-inline`"
|
||||
ref="inlineAttachmentContainer"
|
||||
:data-asset-id="attachment.id"
|
||||
>
|
||||
|
@ -104,6 +105,10 @@ export default {
|
|||
parentId: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div class="list-attachment-container asset"
|
||||
:data-asset-id="attachment.id"
|
||||
:data-e2e="`e2e-CO-${dataE2e}-attachment${attachment.id}-list`"
|
||||
>
|
||||
<i class="text-sn-grey asset-icon sn-icon" :class="attachment.attributes.icon"></i>
|
||||
<a :href="attachment.attributes.urls.blob"
|
||||
|
@ -72,6 +73,10 @@ export default {
|
|||
parentId: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
@mouseenter="handleMouseEnter"
|
||||
@mouseleave="handleMouseLeave"
|
||||
v-click-outside="handleClickOutsideThumbnail"
|
||||
:data-e2e="`e2e-CO-${dataE2e}-attachment${attachment.id}-thumbnail`"
|
||||
>
|
||||
<a :class="{ hidden: showOptions }"
|
||||
:href="attachment.attributes.urls.blob"
|
||||
|
@ -101,7 +102,10 @@
|
|||
>
|
||||
<i class="sn-icon sn-icon-open"></i>
|
||||
</a>
|
||||
<a v-if="attachment.attributes.urls.move" @click.prevent.stop="showMoveModal" class="btn btn-light icon-btn thumbnail-action-btn" :title="i18n.t('attachments.thumbnail.buttons.move')">
|
||||
<a v-if="attachment.attributes.urls.move"
|
||||
@click.prevent.stop="showMoveModal"
|
||||
class="btn btn-light icon-btn thumbnail-action-btn"
|
||||
:title="i18n.t('attachments.thumbnail.buttons.move')">
|
||||
<i class="sn-icon sn-icon-move"></i>
|
||||
</a>
|
||||
<a class="btn btn-light icon-btn thumbnail-action-btn"
|
||||
|
@ -210,6 +214,10 @@ export default {
|
|||
parentId: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -252,7 +260,7 @@ export default {
|
|||
});
|
||||
}
|
||||
return options;
|
||||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
$(this.$nextTick(() => {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<div class="content__checklist-container pr-8" >
|
||||
<div class="content__checklist-container pr-8" :data-e2e="`e2e-CO-${dataE2e}-checklist${element.id}`">
|
||||
<div class="sci-divider my-6" v-if="!inRepository"></div>
|
||||
<div class="checklist-header flex rounded mb-1 items-center relative w-full group/checklist-header" :class="{ 'editing-name': editingName, 'locked': !element.attributes.orderable.urls.update_url }">
|
||||
<div class="checklist-header flex rounded mb-1 items-center relative w-full group/checklist-header"
|
||||
:class="{ 'editing-name': editingName, 'locked': !element.attributes.orderable.urls.update_url }">
|
||||
<div class="grow-1 text-ellipsis whitespace-nowrap grow my-1 font-bold">
|
||||
<InlineEdit
|
||||
:class="{ 'pointer-events-none': !element.attributes.orderable.urls.update_url }"
|
||||
|
@ -13,6 +14,7 @@
|
|||
:autofocus="editingName"
|
||||
:smartAnnotation="true"
|
||||
:attributeName="`${i18n.t('Checklist')} ${i18n.t('name')}`"
|
||||
:dataE2e="`${dataE2e}-checklist${element.id}`"
|
||||
@editingEnabled="editingName = true"
|
||||
@editingDisabled="editingName = false"
|
||||
@update="updateName"
|
||||
|
@ -24,6 +26,7 @@
|
|||
:btnClasses="'btn btn-light icon-btn btn-sm'"
|
||||
:position="'right'"
|
||||
:btnIcon="'sn-icon sn-icon-more-hori'"
|
||||
:dataE2e="`e2e-DD-${dataE2e}-checklist${element.id}-options`"
|
||||
@edit="editingName = true"
|
||||
@duplicate="duplicateElement"
|
||||
@move="showMoveModal"
|
||||
|
@ -51,6 +54,7 @@
|
|||
:reorderChecklistItemUrl="this.element.attributes.orderable.urls.reorder_url"
|
||||
:inRepository="inRepository"
|
||||
:draggable="checklistItems.length > 1"
|
||||
:data-e2e="`${dataE2e}-checklistItem${element.id}`"
|
||||
@editStart="editingItem = true"
|
||||
@editEnd="editingItem = false"
|
||||
@update="saveItem"
|
||||
|
@ -63,6 +67,7 @@
|
|||
<div v-if="element.attributes.orderable.urls.create_item_url && !addingNewItem"
|
||||
class="flex items-center gap-1 text-sn-blue cursor-pointer mb-2 mt-1 "
|
||||
tabindex="0"
|
||||
:data-e2e="`e2e-BT-${dataE2e}-checklist${element.id}-addNew`"
|
||||
@keyup.enter="addItem(checklistItems[checklistItems.length - 1]?.id)"
|
||||
@click="addItem(checklistItems[checklistItems.length - 1]?.id)">
|
||||
<i class="sn-icon sn-icon-new-task w-6 text-center inline-block"></i>
|
||||
|
@ -120,6 +125,10 @@ export default {
|
|||
assignableMyModuleId: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -154,25 +163,29 @@ export default {
|
|||
if (this.element.attributes.orderable.urls.update_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.edit'),
|
||||
emit: 'edit'
|
||||
emit: 'edit',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-checklist${this.element.id}-options-edit`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.duplicate_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.duplicate'),
|
||||
emit: 'duplicate'
|
||||
emit: 'duplicate',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-checklist${this.element.id}-options-duplicate`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.move_targets_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.move'),
|
||||
emit: 'move'
|
||||
emit: 'move',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-checklist${this.element.id}-options-move`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.delete_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.delete'),
|
||||
emit: 'delete'
|
||||
emit: 'delete',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-checklist${this.element.id}-options-delete`
|
||||
});
|
||||
}
|
||||
return menu;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="content__checklist-item pl-10 ml-[-2.325rem] group/checklist-item-header">
|
||||
<div class="content__checklist-item pl-10 ml-[-2.325rem] group/checklist-item-header" :data-e2e="`e2e-CO-${dataE2e}`">
|
||||
<div class="checklist-item-header flex rounded items-center relative w-full" :class="{ 'locked': locked || editingText, 'editing-name': editingText }">
|
||||
<div v-if="reorderChecklistItemUrl"
|
||||
class="absolute h-6 cursor-grab justify-center left-[-2.325rem] top-0.5 px-2 tw-hidden text-sn-grey element-grip step-element-grip--draggable"
|
||||
|
@ -14,7 +14,9 @@
|
|||
type="checkbox"
|
||||
class="sci-checkbox"
|
||||
:disabled="checklistItem.attributes.isNew"
|
||||
:checked="checklistItem.attributes.checked" @change="toggleChecked($event)" />
|
||||
:checked="checklistItem.attributes.checked"
|
||||
:data-e2e="`e2e-CB-${dataE2e}-toggleChecked`"
|
||||
@change="toggleChecked($event)" />
|
||||
<span class="sci-checkbox-label" >
|
||||
</span>
|
||||
</div>
|
||||
|
@ -37,6 +39,7 @@
|
|||
:editOnload="checklistItem.attributes.isNew"
|
||||
:smartAnnotation="true"
|
||||
:allowNewLine="true"
|
||||
:dataE2e="dataE2e"
|
||||
@editingEnabled="enableTextEdit"
|
||||
@editingDisabled="disableTextEdit"
|
||||
@update="updateText"
|
||||
|
@ -46,6 +49,7 @@
|
|||
/>
|
||||
<span v-if="!editingText && (!checklistItem.attributes.urls || deleteUrl)"
|
||||
class="absolute right-0 top-0.5 leading-6 tw-hidden group-hover/checklist-item-header:inline-block !text-sn-blue cursor-pointer"
|
||||
:data-e2e="`e2e-BT-${dataE2e}-delete`"
|
||||
@click="showDeleteModal" tabindex="0">
|
||||
<i class="sn-icon sn-icon-delete"></i>
|
||||
</span>
|
||||
|
@ -88,6 +92,10 @@ export default {
|
|||
reordering: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div class="content__table-container pr-8">
|
||||
<div class="content__table-container pr-8"
|
||||
:data-e2e="`e2e-CO-${dataE2e}-${element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${element.id}`">
|
||||
<div class="sci-divider my-6" v-if="!inRepository"></div>
|
||||
<div class="table-header h-9 flex rounded mb-3 items-center relative w-full group/table-header" :class="{ 'editing-name': editingName, 'locked': locked }">
|
||||
<div v-if="!locked || element.attributes.orderable.name" :key="reloadHeader"
|
||||
|
@ -12,6 +13,7 @@
|
|||
:allowBlank="false"
|
||||
:autofocus="editingName"
|
||||
:attributeName="`${i18n.t('Table')} ${i18n.t('name')}`"
|
||||
:dataE2e="`${dataE2e}-${element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${element.id}`"
|
||||
@editingEnabled="enableNameEdit"
|
||||
@editingDisabled="disableNameEdit"
|
||||
@update="updateName"
|
||||
|
@ -23,6 +25,7 @@
|
|||
:btnClasses="'btn btn-light icon-btn btn-sm'"
|
||||
:position="'right'"
|
||||
:btnIcon="'sn-icon sn-icon-more-hori'"
|
||||
:dataE2e="`e2e-DD-${dataE2e}-${element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${element.id}-options`"
|
||||
@edit="enableNameEdit"
|
||||
@duplicate="duplicateElement"
|
||||
@move="showMoveModal"
|
||||
|
@ -32,11 +35,14 @@
|
|||
<div class="table-body group/table-body relative border-solid border-transparent"
|
||||
:class="{'edit border-sn-light-grey': editingTable, 'view': !editingTable, 'locked': !element.attributes.orderable.urls.update_url}"
|
||||
tabindex="0"
|
||||
:data_e2e="`e2e-TB-${dataE2e}-${element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${element.id}`"
|
||||
@keyup.enter="!editingTable && enableTableEdit()">
|
||||
<div ref="hotTable" class="hot-table-container" @click="!editingTable && enableTableEdit()">
|
||||
</div>
|
||||
<div class="text-xs pt-3 pb-2 text-sn-grey h-1">
|
||||
<span v-if="editingTable">{{ i18n.t('protocols.steps.table.edit_message') }}</span>
|
||||
<span v-if="editingTable" :dataE2e="`e2e-TX-${dataE2e}-${element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${element.id}-editMessage`">
|
||||
{{ i18n.t('protocols.steps.table.edit_message') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<deleteElementModal v-if="confirmingDelete" @confirm="deleteElement" @cancel="closeDeleteModal"/>
|
||||
|
@ -82,6 +88,10 @@ export default {
|
|||
assignableMyModuleId: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -104,25 +114,29 @@ export default {
|
|||
if (this.element.attributes.orderable.urls.update_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.edit'),
|
||||
emit: 'edit'
|
||||
emit: 'edit',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-${this.element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${this.element.id}-options-edit`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.duplicate_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.duplicate'),
|
||||
emit: 'duplicate'
|
||||
emit: 'duplicate',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-${this.element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${this.element.id}-options-duplicate`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.move_targets_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.move'),
|
||||
emit: 'move'
|
||||
emit: 'move',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-${this.element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${this.element.id}-options-move`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.delete_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.delete'),
|
||||
emit: 'delete'
|
||||
emit: 'delete',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-${this.element.attributes.orderable.metadata.plateTemplate ? 'wellPlate' : 'table'}${this.element.id}-options-delete`
|
||||
});
|
||||
}
|
||||
return menu;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<template>
|
||||
<div class="content__text-container pr-8">
|
||||
<div class="content__text-container pr-8" :data-e2e="`e2e-CO-${dataE2e}-stepText${element.id}`">
|
||||
<div class="sci-divider my-6" v-if="!inRepository"></div>
|
||||
<div class="text-header h-9 flex rounded mb-1 items-center relative w-full group/text-header" :class="{ 'editing-name': editingName, 'locked': !element.attributes.orderable.urls.update_url }">
|
||||
<div class="text-header h-9 flex rounded mb-1 items-center relative w-full group/text-header"
|
||||
:class="{ 'editing-name': editingName,
|
||||
'locked': !element.attributes.orderable.urls.update_url }">
|
||||
<div v-if="element.attributes.orderable.urls.update_url || element.attributes.orderable.name"
|
||||
class="grow-1 text-ellipsis whitespace-nowrap grow my-1 font-bold"
|
||||
:class="{'pointer-events-none': !element.attributes.orderable.urls.update_url}"
|
||||
|
@ -13,6 +15,7 @@
|
|||
:allowBlank="true"
|
||||
:autofocus="editingName"
|
||||
:attributeName="`${i18n.t('Text')} ${i18n.t('name')}`"
|
||||
:dataE2e="`${dataE2e}-stepText${element.id}`"
|
||||
@editingEnabled="enableNameEdit"
|
||||
@editingDisabled="disableNameEdit"
|
||||
@update="updateName"
|
||||
|
@ -24,13 +27,18 @@
|
|||
:btnClasses="'btn btn-light icon-btn btn-sm'"
|
||||
:position="'right'"
|
||||
:btnIcon="'sn-icon sn-icon-more-hori'"
|
||||
:dataE2e="`e2e-DD-${dataE2e}-stepText${element.id}-options`"
|
||||
@edit="enableNameEdit"
|
||||
@duplicate="duplicateElement"
|
||||
@move="showMoveModal"
|
||||
@delete="showDeleteModal"
|
||||
></MenuDropdown>
|
||||
</div>
|
||||
<div class="flex rounded min-h-[2.25rem] mb-4 relative group/text_container content__text-body" :class="{ 'edit': inEditMode, 'component__element--locked': !element.attributes.orderable.urls.update_url }" @keyup.enter="enableEditMode($event)" tabindex="0">
|
||||
<div class="flex rounded min-h-[2.25rem] mb-4 relative group/text_container content__text-body"
|
||||
:class="{ 'edit': inEditMode, 'component__element--locked': !element.attributes.orderable.urls.update_url }"
|
||||
:data-e2e="`e2e-IF-${dataE2e}-stepText${element.id}`"
|
||||
@keyup.enter="enableEditMode($event)"
|
||||
tabindex="0">
|
||||
<Tinymce
|
||||
v-if="element.attributes.orderable.urls.update_url"
|
||||
:value="element.attributes.orderable.text"
|
||||
|
@ -48,8 +56,8 @@
|
|||
@editingDisabled="disableEditMode"
|
||||
@editingEnabled="enableEditMode"
|
||||
/>
|
||||
<div class="view-text-element" v-else-if="element.attributes.orderable.text_view" v-html="wrappedTables"></div>
|
||||
<div v-else class="text-sn-grey">
|
||||
<div class="view-text-element" v-else-if="element.attributes.orderable.text_view" v-html="wrappedTables" :data-e2e="`e2e-TX-${dataE2e}-stepText${element.id}`"></div>
|
||||
<div v-else class="text-sn-grey" :data-e2e="`e2e-TX-${dataE2e}-stepText${element.id}-empty`">
|
||||
{{ i18n.t("protocols.steps.text.empty_text") }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -96,6 +104,10 @@ export default {
|
|||
assignableMyModuleId: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
|
@ -126,25 +138,29 @@ export default {
|
|||
if (this.element.attributes.orderable.urls.update_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.edit'),
|
||||
emit: 'edit'
|
||||
emit: 'edit',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-stepText${this.element.id}-options-edit`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.duplicate_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.duplicate'),
|
||||
emit: 'duplicate'
|
||||
emit: 'duplicate',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-stepText${this.element.id}-options-duplicate`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.move_targets_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.move'),
|
||||
emit: 'move'
|
||||
emit: 'move',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-stepText${this.element.id}-options-move`
|
||||
});
|
||||
}
|
||||
if (this.element.attributes.orderable.urls.delete_url) {
|
||||
menu.push({
|
||||
text: I18n.t('general.delete'),
|
||||
emit: 'delete'
|
||||
emit: 'delete',
|
||||
data_e2e: `e2e-BT-${this.dataE2e}-stepText${this.element.id}-options-delete`
|
||||
});
|
||||
}
|
||||
return menu;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div v-if="pages.length > 1" class="flex gap-3 select-none">
|
||||
<div class="w-9 h-9">
|
||||
<div class="w-9 h-9 cursor-pointer flex items-center justify-center"
|
||||
<div class="w-9 h-9 cursor-pointer flex items-center justify-center" data-e2e="e2e-BT-tableInfo-left"
|
||||
@click="$emit('setPage', currentPage - 1)"
|
||||
v-if="currentPage > 1">
|
||||
<i class="sn-icon sn-icon-left cursor-pointer"></i>
|
||||
|
@ -11,11 +11,12 @@
|
|||
v-for="page in pages"
|
||||
:class="{ 'border-solid rounded border-sn-science-blue': page === currentPage }"
|
||||
:key="page"
|
||||
:data-e2e="`e2e-BT-tableInfo-page-${page}`"
|
||||
@click="$emit('setPage', page)">
|
||||
<span >{{ page }}</span>
|
||||
</div>
|
||||
<div class="w-9 h-9">
|
||||
<div class="w-9 h-9 cursor-pointer flex items-center justify-center"
|
||||
<div class="w-9 h-9 cursor-pointer flex items-center justify-center" data-e2e="e2e-BT-tableInfo-right"
|
||||
@click="$emit('setPage', currentPage + 1)"
|
||||
v-if="totalPage > currentPage">
|
||||
<i class="sn-icon sn-icon-right cursor-pointer"></i>
|
||||
|
@ -30,12 +31,12 @@ export default {
|
|||
props: {
|
||||
totalPage: {
|
||||
type: Number,
|
||||
required: true,
|
||||
required: true
|
||||
},
|
||||
currentPage: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pages() {
|
||||
|
@ -50,7 +51,7 @@ export default {
|
|||
}
|
||||
}
|
||||
return pages;
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -75,17 +75,18 @@
|
|||
:params="actionsParams"
|
||||
@toolbar:action="emitAction" />
|
||||
</div>
|
||||
<div v-if="scrollMode == 'pages'" class="flex items-center py-4" :class="{'opacity-0': initializing }">
|
||||
<div class="flex items-center gap-4">
|
||||
<div v-if="scrollMode == 'pages'" class="flex items-center py-4" :class="{'opacity-0': initializing }" data-e2e="e2e-CO-tableInfo">
|
||||
<div class="flex items-center gap-4" data-e2e="e2e-TX-tableInfo-show">
|
||||
{{ i18n.t('datatable.show') }}
|
||||
<div class="w-36">
|
||||
<SelectDropdown
|
||||
:value="perPage"
|
||||
:options="perPageOptions"
|
||||
:data-e2e="'e2e-DD-tableInfo-rows'"
|
||||
@change="setPerPage"
|
||||
></SelectDropdown>
|
||||
</div>
|
||||
<div v-show="!dataLoading">
|
||||
<div v-show="!dataLoading" data-e2e="e2e-TX-tableInfo-entries">
|
||||
<span v-if="selectedRows.length">
|
||||
{{ i18n.t('datatable.entries.selected', { count: totalEntries, selected: selectedRows.length }) }}
|
||||
</span>
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
:btnIcon="action.icon"
|
||||
:caret="true"
|
||||
:position="'right'"
|
||||
:data-e2e="`e2e-BT-topToolbar-${action.name}`"
|
||||
@dtEvent="handleEvent"
|
||||
></MenuDropdown>
|
||||
</template>
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
'border-b-sn-science-blue': !error,
|
||||
}"
|
||||
v-model="newValue"
|
||||
:data-e2e="`e2e-IF-${dataE2e}`"
|
||||
@keydown="handleKeypress"
|
||||
@blur="handleBlur"
|
||||
@keyup.escape="cancelEdit && this.atWhoOpened"
|
||||
|
@ -27,6 +28,7 @@
|
|||
}"
|
||||
:placeholder="placeholder"
|
||||
v-model="newValue"
|
||||
:data-e2e="`e2e-IF-${dataE2e}`"
|
||||
@keydown="handleKeypress"
|
||||
@blur="handleBlur"
|
||||
@keyup.escape="cancelEdit && this.atWhoOpened"
|
||||
|
@ -38,6 +40,7 @@
|
|||
ref="view"
|
||||
class="grid sci-cursor-edit leading-5 border-0 outline-none border-solid border-y border-transparent"
|
||||
:class="{ 'text-sn-grey font-normal': isBlank, 'whitespace-pre-line py-1': !singleLine }"
|
||||
:data-e2e="`e2e-TX-${dataE2e}`"
|
||||
@click="enableEdit($event)"
|
||||
>
|
||||
<span :class="{'truncate': singleLine }" :title="sa_value || placeholder" v-if="smartAnnotation" v-html="sa_value || placeholder" ></span>
|
||||
|
@ -48,6 +51,7 @@
|
|||
class="mt-2 whitespace-nowrap truncate text-xs font-normal absolute bottom-[-1rem] w-full"
|
||||
:title="editing && error ? error : timestamp"
|
||||
:class="{'text-sn-delete-red': editing && error}"
|
||||
:data-e2e="`e2e-TX-${dataE2e}-timestampError`"
|
||||
>
|
||||
{{ editing && error ? error : timestamp }}
|
||||
</div>
|
||||
|
@ -76,7 +80,8 @@ export default {
|
|||
editOnload: { type: Boolean, default: false },
|
||||
defaultValue: { type: String, default: '' },
|
||||
singleLine: { type: Boolean, default: true },
|
||||
preventLeavingUntilFilled: { type: Boolean, default: false }
|
||||
preventLeavingUntilFilled: { type: Boolean, default: false },
|
||||
dataE2e: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="dropdown-selector">
|
||||
<div class="dropdown-selector" :data-e2e="`e2e-IF-${dataE2e}`">
|
||||
<select :id="this.selectorId"
|
||||
:data-select-by-group="groupSelector"
|
||||
:data-combine-tags="dataCombineTags"
|
||||
|
@ -111,6 +111,10 @@ export default {
|
|||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
dataE2e: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
onChange: Function
|
||||
|
||||
},
|
||||
|
@ -127,6 +131,7 @@ export default {
|
|||
tagLabel: this.tagLabel,
|
||||
labelHTML: this.labelHTML,
|
||||
onOpen: this.onOpen,
|
||||
dataE2e: this.dataE2e,
|
||||
onChange: () => {
|
||||
if (this.onChange) this.onChange();
|
||||
this.selectChanged(dropdownSelector.getValues(`#${this.selectorId}`));
|
||||
|
|
11
app/javascript/vue/shared/loading.vue
Normal file
11
app/javascript/vue/shared/loading.vue
Normal file
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<div class="flex absolute top-0 items-center justify-center w-full flex-grow h-full z-[3000]">
|
||||
<div class="absolute top-0 left-0 w-full h-full bg-black opacity-10"></div>
|
||||
<img src="/images/medium/loading.svg" alt="Loading" class="" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'Loading'
|
||||
};
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="relative" v-if="listItems.length > 0 || alwaysShow" v-click-outside="closeMenu" >
|
||||
<button ref="field" :class="btnClasses" :title="title" @click="isOpen = !isOpen" :data-e2e="e2eSortButton">
|
||||
<button ref="field" :class="btnClasses" :title="title" @click="isOpen = !isOpen" :data-e2e="dataE2e">
|
||||
<i v-if="btnIcon" :class="btnIcon"></i>
|
||||
{{ btnText }}
|
||||
<i v-if="caret && isOpen" class="sn-icon sn-icon-up"></i>
|
||||
|
@ -35,6 +35,7 @@
|
|||
:class="{ 'bg-sn-super-light-blue': item.active }"
|
||||
class="flex group items-center rounded relative text-sn-blue whitespace-nowrap px-3 py-2.5 hover:no-underline cursor-pointer
|
||||
group-hover:bg-sn-super-light-blue hover:!bg-sn-super-light-grey"
|
||||
:data-e2e="item.data_e2e"
|
||||
>
|
||||
{{ item.text }}
|
||||
<i class="sn-icon sn-icon-right ml-auto"></i>
|
||||
|
@ -50,6 +51,7 @@
|
|||
:href="sub_item.url"
|
||||
:traget="sub_item.url_target || '_self'"
|
||||
:class="{ 'bg-sn-super-light-blue': item.active }"
|
||||
:data-e2e="`${sub_item.data_e2e}`"
|
||||
class="block whitespace-nowrap rounded px-3 py-2.5 hover:!text-sn-blue hover:no-underline cursor-pointer hover:bg-sn-super-light-grey leading-5"
|
||||
@click="handleClick($event, sub_item)"
|
||||
>
|
||||
|
@ -80,7 +82,7 @@ export default {
|
|||
caret: { type: Boolean, default: false },
|
||||
alwaysShow: { type: Boolean, default: false },
|
||||
title: { type: String, default: '' },
|
||||
e2eSortButton: { type: String, default: '' }
|
||||
dataE2e: { type: String, default: '' }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
|
@ -273,8 +273,13 @@ export default {
|
|||
});
|
||||
}
|
||||
},
|
||||
urlParams() {
|
||||
this.fetchOptions();
|
||||
urlParams: {
|
||||
handler(oldVal, newVal) {
|
||||
if (!this.compareObjects(oldVal, newVal)) {
|
||||
this.fetchOptions();
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -384,6 +389,11 @@ export default {
|
|||
if (this.$refs.options) {
|
||||
this.$refs.options[this.focusedOption]?.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
},
|
||||
compareObjects(o1, o2) {
|
||||
const normalizedObj1 = Object.fromEntries(Object.entries(o1).sort(([k1], [k2]) => k1.localeCompare(k2)));
|
||||
const normalizedObj2 = Object.fromEntries(Object.entries(o2).sort(([k1], [k2]) => k1.localeCompare(k2)));
|
||||
return JSON.stringify(normalizedObj1) === JSON.stringify(normalizedObj2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -46,7 +46,7 @@ class RepositoriesExportJob < ApplicationJob
|
|||
FileUtils.mkdir_p(attachments_path)
|
||||
|
||||
# File creation
|
||||
file_name = FileUtils.touch("#{path}/#{repository_name}.#{@file_type}").first
|
||||
repository_items_file_name = FileUtils.touch("#{path}/#{repository_name}.#{@file_type}").first
|
||||
|
||||
# Define headers and columns IDs
|
||||
col_ids = [-3, -4, -5, -6, -7, -8, -9, -10]
|
||||
|
@ -69,10 +69,10 @@ class RepositoriesExportJob < ApplicationJob
|
|||
|
||||
# Generate CSV / XLSX
|
||||
service = RepositoryExportService
|
||||
.new(@file_type, repository.repository_rows, col_ids, @user, repository, in_module: handle_name_func)
|
||||
.new(@file_type, repository.repository_rows, col_ids, @user, repository, handle_name_func)
|
||||
exported_data = service.export!
|
||||
|
||||
File.binwrite(file_name, exported_data)
|
||||
File.binwrite(repository_items_file_name, exported_data)
|
||||
|
||||
# Save all attachments (it doesn't work directly in callback function
|
||||
assets.each do |asset, asset_path|
|
||||
|
|
|
@ -35,15 +35,9 @@ class RepositoryZipExportJob < ZipExportJob
|
|||
params[:header_ids].map(&:to_i),
|
||||
@user,
|
||||
repository,
|
||||
in_module: params[:my_module_id].present?,
|
||||
empty_export: @empty_export)
|
||||
in_module: params[:my_module_id].present?)
|
||||
exported_data = service.export!
|
||||
|
||||
if @empty_export
|
||||
File.binwrite("#{dir}/Export_Inventory_Empty_#{Time.now.utc.strftime('%F %H-%M-%S_UTC')}.#{@file_type}", exported_data)
|
||||
else
|
||||
File.binwrite("#{dir}/export.#{@file_type}", exported_data)
|
||||
end
|
||||
File.binwrite("#{dir}/export.#{@file_type}", exported_data)
|
||||
end
|
||||
|
||||
def failed_notification_title
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
class ZipExportJob < ApplicationJob
|
||||
include FailedDeliveryNotifiableJob
|
||||
|
||||
def perform(user_id:, params: {}, file_type: :csv, empty_export: false)
|
||||
def perform(user_id:, params: {}, file_type: :csv)
|
||||
@user = User.find(user_id)
|
||||
@file_type = file_type.to_sym
|
||||
@empty_export = empty_export
|
||||
I18n.backend.date_format = @user.settings[:date_format] || Constants::DEFAULT_DATE_FORMAT
|
||||
zip_input_dir = FileUtils.mkdir_p(Rails.root.join("tmp/temp_zip_#{Time.now.to_i}").to_s).first
|
||||
zip_dir = FileUtils.mkdir_p(Rails.root.join('tmp/zip-ready').to_s).first
|
||||
|
|
|
@ -245,7 +245,7 @@ class Asset < ApplicationRecord
|
|||
|
||||
def can_perform_action(action)
|
||||
if ENV['WOPI_ENABLED'] == 'true'
|
||||
file_ext = file_name.split('.').last
|
||||
file_ext = file_name.split('.').last&.downcase
|
||||
|
||||
if file_ext == 'wopitest' &&
|
||||
(!ENV['WOPI_TEST_ENABLED'] || ENV['WOPI_TEST_ENABLED'] == 'false')
|
||||
|
@ -297,7 +297,7 @@ class Asset < ApplicationRecord
|
|||
end
|
||||
|
||||
def favicon_url(action)
|
||||
file_ext = file_name.split('.').last
|
||||
file_ext = file_name.split('.').last&.downcase
|
||||
action = get_action(file_ext, action)
|
||||
action[:icon] if action[:icon]
|
||||
end
|
||||
|
|
|
@ -56,7 +56,6 @@ class MyModule < ApplicationRecord
|
|||
belongs_to :changing_from_my_module_status, optional: true, class_name: 'MyModuleStatus'
|
||||
delegate :my_module_status_flow, to: :my_module_status, allow_nil: true
|
||||
has_many :results, inverse_of: :my_module, dependent: :destroy
|
||||
has_many :results_include_discarded, -> { with_discarded }, class_name: 'Result', inverse_of: :my_module
|
||||
has_many :my_module_tags, inverse_of: :my_module, dependent: :destroy
|
||||
has_many :tags, through: :my_module_tags, dependent: :destroy
|
||||
has_many :task_comments, foreign_key: :associated_id, dependent: :destroy
|
||||
|
|
|
@ -162,6 +162,7 @@ class Repository < RepositoryBase
|
|||
def importable_repository_fields
|
||||
fields = {}
|
||||
# First and foremost add record name
|
||||
fields['0'] = I18n.t('repositories.id_column')
|
||||
fields['-1'] = I18n.t('repositories.default_column')
|
||||
# Add all other custom columns
|
||||
repository_columns.order(:created_at).each do |rc|
|
||||
|
@ -203,11 +204,6 @@ class Repository < RepositoryBase
|
|||
new_repo
|
||||
end
|
||||
|
||||
def import_records(sheet, mappings, user, can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
importer = RepositoryImportParser::Importer.new(sheet, mappings, user, self)
|
||||
importer.run(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
end
|
||||
|
||||
def assigned_rows(my_module)
|
||||
repository_rows.joins(:my_module_repository_rows).where(my_module_repository_rows: { my_module_id: my_module.id })
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
class RepositoryCell < ApplicationRecord
|
||||
include ReminderRepositoryCellJoinable
|
||||
|
||||
attr_accessor :importing
|
||||
attr_accessor :importing, :to_destroy
|
||||
|
||||
belongs_to :repository_row, touch: true
|
||||
belongs_to :repository_column
|
||||
|
|
|
@ -105,6 +105,8 @@ class RepositoryRow < ApplicationRecord
|
|||
length: { maximum: Constants::NAME_MAX_LENGTH }
|
||||
validates :created_by, presence: true
|
||||
|
||||
attr_accessor :import_status, :import_message
|
||||
|
||||
scope :active, -> { where(archived: false) }
|
||||
scope :archived, -> { where(archived: true) }
|
||||
|
||||
|
|
|
@ -5,9 +5,6 @@ class Result < ApplicationRecord
|
|||
include SearchableModel
|
||||
include SearchableByNameModel
|
||||
include ViewableModel
|
||||
include Discard::Model
|
||||
|
||||
default_scope -> { kept }
|
||||
|
||||
auto_strip_attributes :name, nullify: false
|
||||
validates :name, length: { maximum: Constants::NAME_MAX_LENGTH }
|
||||
|
|
|
@ -19,6 +19,7 @@ module Lists
|
|||
status
|
||||
designated_users
|
||||
tags
|
||||
tags_html
|
||||
comments
|
||||
due_date_formatted
|
||||
permissions
|
||||
|
@ -142,6 +143,17 @@ module Lists
|
|||
end
|
||||
end
|
||||
|
||||
def tags_html
|
||||
# legacy canvas support
|
||||
return '' unless @instance_options[:controller]
|
||||
|
||||
@instance_options[:controller].render_to_string(
|
||||
partial: 'canvas/tags',
|
||||
locals: { my_module: object },
|
||||
formats: :html
|
||||
)
|
||||
end
|
||||
|
||||
def comments
|
||||
@user = scope[:user] || @instance_options[:user]
|
||||
{
|
||||
|
|
19
app/serializers/repository_cell_import_serializer.rb
Normal file
19
app/serializers/repository_cell_import_serializer.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryCellImportSerializer < ActiveModel::Serializer
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :id, :value, :changes, :repository_column_id, :formatted_value
|
||||
|
||||
def changes
|
||||
object.value.changes
|
||||
end
|
||||
|
||||
def value
|
||||
object.value if !object.to_destroy
|
||||
end
|
||||
|
||||
def formatted_value
|
||||
object.value.formatted if !object.to_destroy
|
||||
end
|
||||
end
|
|
@ -3,11 +3,7 @@
|
|||
class RepositoryCellSerializer < ActiveModel::Serializer
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :id, :value, :changes, :repository_column_id, :formatted_value
|
||||
|
||||
def changes
|
||||
object.value.changes
|
||||
end
|
||||
attributes :id, :value, :repository_column_id, :formatted_value
|
||||
|
||||
def value
|
||||
object.value
|
||||
|
|
13
app/serializers/repository_row_import_serializer.rb
Normal file
13
app/serializers/repository_row_import_serializer.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryRowImportSerializer < ActiveModel::Serializer
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :id, :name, :code, :import_status, :import_message
|
||||
|
||||
has_many :repository_cells, serializer: RepositoryCellImportSerializer
|
||||
|
||||
attribute :code do
|
||||
object.new_record? ? nil : object.code
|
||||
end
|
||||
end
|
|
@ -5,5 +5,6 @@ class RepositoryRowSerializer < ActiveModel::Serializer
|
|||
|
||||
attributes :id, :name, :code
|
||||
|
||||
has_many :repository_cells, serializer: RepositoryCellSerializer
|
||||
has_many :repository_cells, serializer: RepositoryCellImportSerializer
|
||||
|
||||
end
|
||||
|
|
|
@ -55,20 +55,9 @@ class ActivitiesService
|
|||
child_model = parent_model.reflect_on_association(child).class_name.to_sym
|
||||
next if subjects[child_model]
|
||||
|
||||
|
||||
if subject_name == 'Result'
|
||||
parent_model = parent_model.with_discarded
|
||||
end
|
||||
|
||||
if child == :results
|
||||
subjects[child_model] = parent_model.where(id: subjects[subject_name])
|
||||
.joins(:results_include_discarded)
|
||||
.pluck('results.id')
|
||||
else
|
||||
subjects[child_model] = parent_model.where(id: subjects[subject_name])
|
||||
.joins(child)
|
||||
.pluck("#{child.to_s.pluralize}.id")
|
||||
end
|
||||
subjects[child_model] = parent_model.where(id: subjects[subject_name])
|
||||
.joins(child)
|
||||
.pluck("#{child.to_s.pluralize}.id")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ImportRepository
|
||||
class ImportRecords
|
||||
def initialize(options)
|
||||
|
@ -6,60 +8,25 @@ module ImportRepository
|
|||
@mappings = options.fetch(:mappings)
|
||||
@session = options.fetch(:session)
|
||||
@user = options.fetch(:user)
|
||||
@can_edit_existing_items = options.fetch(:can_edit_existing_items)
|
||||
@should_overwrite_with_empty_cells = options.fetch(:should_overwrite_with_empty_cells)
|
||||
@preview = options.fetch(:preview)
|
||||
end
|
||||
|
||||
def import!(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
status = run_import_actions(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
#@temp_file.destroy
|
||||
def import!
|
||||
status = @temp_file.file.open do |temp_file|
|
||||
importer = RepositoryImportParser::Importer.new(SpreadsheetParser.open_spreadsheet(temp_file),
|
||||
@mappings,
|
||||
@user,
|
||||
@repository,
|
||||
@can_edit_existing_items,
|
||||
@should_overwrite_with_empty_cells,
|
||||
@preview)
|
||||
importer.run
|
||||
end
|
||||
|
||||
@temp_file.destroy unless @preview
|
||||
status
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def run_import_actions(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
@temp_file.file.open do |temp_file|
|
||||
@repository.import_records(
|
||||
SpreadsheetParser.open_spreadsheet(temp_file),
|
||||
@mappings,
|
||||
@user,
|
||||
can_edit_existing_items,
|
||||
should_overwrite_with_empty_cells,
|
||||
preview
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def run_checks
|
||||
unless @mappings
|
||||
return {
|
||||
status: :error,
|
||||
errors:
|
||||
I18n.t('repositories.import_records.error_message.no_data_to_parse')
|
||||
}
|
||||
end
|
||||
unless @mappings.value?('-1')
|
||||
return {
|
||||
status: :error,
|
||||
errors:
|
||||
I18n.t('repositories.import_records.error_message.no_column_name')
|
||||
}
|
||||
end
|
||||
unless @temp_file
|
||||
return {
|
||||
status: :error,
|
||||
errors:
|
||||
I18n.t(
|
||||
'repositories.import_records.error_message.temp_file_not_found'
|
||||
)
|
||||
}
|
||||
end
|
||||
unless @temp_file.session_id == session.id
|
||||
return {
|
||||
status: :error,
|
||||
errors:
|
||||
I18n.t('repositories.import_records.error_message.session_expired')
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
require 'csv'
|
||||
|
||||
module RepositoryCsvExport
|
||||
def self.to_csv(rows, column_ids, user, repository, handle_file_name_func, in_module, empty_export)
|
||||
def self.to_csv(rows, column_ids, user, repository, handle_file_name_func, in_module)
|
||||
# Parse column names
|
||||
csv_header = []
|
||||
add_consumption = in_module && !repository.is_a?(RepositorySnapshot) && repository.has_stock_management?
|
||||
|
@ -38,47 +38,45 @@ module RepositoryCsvExport
|
|||
|
||||
CSV.generate do |csv|
|
||||
csv << csv_header
|
||||
unless empty_export
|
||||
rows.each do |row|
|
||||
csv_row = []
|
||||
column_ids.each do |c_id|
|
||||
case c_id
|
||||
when -1, -2
|
||||
next
|
||||
when -3
|
||||
csv_row << (repository.is_a?(RepositorySnapshot) ? row.parent_id : row.code)
|
||||
when -4
|
||||
csv_row << row.name
|
||||
when -5
|
||||
csv_row << row.created_by.full_name
|
||||
when -6
|
||||
csv_row << I18n.l(row.created_at, format: :full)
|
||||
when -7
|
||||
csv_row << row.updated_at ? I18n.l(row.updated_at, format: :full) : ''
|
||||
when -8
|
||||
csv_row << row.last_modified_by.full_name
|
||||
when -9
|
||||
csv_row << (row.archived? && row.archived_by.present? ? row.archived_by.full_name : '')
|
||||
when -10
|
||||
csv_row << (row.archived? && row.archived_on.present? ? I18n.l(row.archived_on, format: :full) : '')
|
||||
when -11
|
||||
csv_row << row.parent_repository_rows.map(&:code).join(' | ')
|
||||
csv_row << row.child_repository_rows.map(&:code).join(' | ')
|
||||
else
|
||||
cell = row.repository_cells.find_by(repository_column_id: c_id)
|
||||
rows.each do |row|
|
||||
csv_row = []
|
||||
column_ids.each do |c_id|
|
||||
case c_id
|
||||
when -1, -2
|
||||
next
|
||||
when -3
|
||||
csv_row << (repository.is_a?(RepositorySnapshot) ? row.parent_id : row.code)
|
||||
when -4
|
||||
csv_row << row.name
|
||||
when -5
|
||||
csv_row << row.created_by.full_name
|
||||
when -6
|
||||
csv_row << I18n.l(row.created_at, format: :full)
|
||||
when -7
|
||||
csv_row << row.updated_at ? I18n.l(row.updated_at, format: :full) : ''
|
||||
when -8
|
||||
csv_row << row.last_modified_by.full_name
|
||||
when -9
|
||||
csv_row << (row.archived? && row.archived_by.present? ? row.archived_by.full_name : '')
|
||||
when -10
|
||||
csv_row << (row.archived? && row.archived_on.present? ? I18n.l(row.archived_on, format: :full) : '')
|
||||
when -11
|
||||
csv_row << row.parent_repository_rows.map(&:code).join(' | ')
|
||||
csv_row << row.child_repository_rows.map(&:code).join(' | ')
|
||||
else
|
||||
cell = row.repository_cells.find_by(repository_column_id: c_id)
|
||||
|
||||
csv_row << if cell
|
||||
if cell.value_type == 'RepositoryAssetValue' && handle_file_name_func
|
||||
handle_file_name_func.call(cell.value.asset)
|
||||
else
|
||||
cell.value.export_formatted
|
||||
end
|
||||
if cell.value_type == 'RepositoryAssetValue' && handle_file_name_func
|
||||
handle_file_name_func.call(cell.value.asset)
|
||||
else
|
||||
cell.value.export_formatted
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
csv_row << row.row_consumption(row.stock_consumption) if add_consumption
|
||||
csv << csv_row
|
||||
end
|
||||
csv_row << row.row_consumption(row.stock_consumption) if add_consumption
|
||||
csv << csv_row
|
||||
end
|
||||
end.encode('UTF-8', invalid: :replace, undef: :replace)
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoryExportService
|
||||
def initialize(file_type, rows, columns, user, repository, handle_name_func = nil, in_module: false, empty_export: false)
|
||||
def initialize(file_type, rows, columns, user, repository, handle_name_func = nil, in_module: false)
|
||||
@file_type = file_type
|
||||
@user = user
|
||||
@rows = rows
|
||||
|
@ -9,13 +9,12 @@ class RepositoryExportService
|
|||
@repository = repository
|
||||
@handle_name_func = handle_name_func
|
||||
@in_module = in_module
|
||||
@empty_export = empty_export
|
||||
end
|
||||
|
||||
def export!
|
||||
case @file_type
|
||||
when :csv
|
||||
file_data = RepositoryCsvExport.to_csv(@rows, @columns, @user, @repository, @handle_name_func, @in_module, @empty_export)
|
||||
file_data = RepositoryCsvExport.to_csv(@rows, @columns, @user, @repository, @handle_name_func, @in_module)
|
||||
when :xlsx
|
||||
file_data = RepositoryXlsxExport.to_xlsx(@rows, @columns, @user, @repository, @handle_name_func, @in_module)
|
||||
end
|
||||
|
|
|
@ -36,9 +36,9 @@ module RepositoryXlsxExport
|
|||
when -6
|
||||
row_data << I18n.l(row.created_at, format: :full)
|
||||
when -7
|
||||
csv_row << row.updated_at ? I18n.l(row.updated_at, format: :full) : ''
|
||||
row_data << row.updated_at ? I18n.l(row.updated_at, format: :full) : ''
|
||||
when -8
|
||||
csv_row << row.last_modified_by.full_name
|
||||
row_data << row.last_modified_by.full_name
|
||||
when -9
|
||||
row_data << (row.archived? && row.archived_by.present? ? row.archived_by.full_name : '')
|
||||
when -10
|
||||
|
|
|
@ -11,9 +11,10 @@ module RepositoryImportParser
|
|||
class Importer
|
||||
IMPORT_BATCH_SIZE = 500
|
||||
|
||||
def initialize(sheet, mappings, user, repository)
|
||||
def initialize(sheet, mappings, user, repository, can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
@columns = []
|
||||
@name_index = -1
|
||||
@id_index = nil
|
||||
@total_new_rows = 0
|
||||
@new_rows_added = 0
|
||||
@header_skipped = false
|
||||
|
@ -23,31 +24,31 @@ module RepositoryImportParser
|
|||
@mappings = mappings
|
||||
@user = user
|
||||
@repository_columns = @repository.repository_columns
|
||||
@can_edit_existing_items = true # can_edit_existing_items
|
||||
@should_overwrite_with_empty_cells = true # should_overwrite_with_empty_cells
|
||||
@preview = preview
|
||||
end
|
||||
|
||||
def run(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
def run
|
||||
fetch_columns
|
||||
return check_for_duplicate_columns if check_for_duplicate_columns
|
||||
|
||||
import_rows!(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
import_rows!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_columns
|
||||
@mappings.each_with_index do |(_, value), index|
|
||||
value = JSON.parse(value) rescue value
|
||||
value = value.to_s unless value.is_a?(Hash)
|
||||
|
||||
if value == '-1'
|
||||
# Fill blank space, so our indices stay the same
|
||||
case value
|
||||
when '0'
|
||||
@columns << nil
|
||||
@id_index = index
|
||||
when '-1'
|
||||
@columns << nil
|
||||
@name_index = index
|
||||
|
||||
# creating a custom option column
|
||||
elsif value.is_a?(Hash)
|
||||
new_repository_column = @repository.repository_columns.create!(created_by: @user, name: value['name']+rand(10000).to_s, data_type: "Repository#{value['type']}Value")
|
||||
@columns << new_repository_column
|
||||
else
|
||||
@columns << @repository_columns.where(data_type: Extends::REPOSITORY_IMPORTABLE_TYPES)
|
||||
.preload(Extends::REPOSITORY_IMPORT_COLUMN_PRELOADS)
|
||||
|
@ -63,191 +64,190 @@ module RepositoryImportParser
|
|||
end
|
||||
end
|
||||
|
||||
def import_rows!(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
errors = false
|
||||
duplicate_ids = SpreadsheetParser.duplicate_ids(@sheet)
|
||||
|
||||
imported_rows = []
|
||||
|
||||
@repository.transaction do
|
||||
batch_counter = 0
|
||||
full_row_import_batch = []
|
||||
|
||||
@rows.each do |row|
|
||||
# Skip empty rows
|
||||
next if row.blank?
|
||||
|
||||
# Skip duplicates
|
||||
next if duplicate_ids.include?(row.first)
|
||||
|
||||
unless @header_skipped
|
||||
@header_skipped = true
|
||||
next
|
||||
end
|
||||
@total_new_rows += 1
|
||||
|
||||
new_full_row = {}
|
||||
incoming_row = SpreadsheetParser.parse_row(
|
||||
row,
|
||||
@sheet,
|
||||
date_format: @user.settings['date_format']
|
||||
)
|
||||
|
||||
incoming_row.each_with_index do |value, index|
|
||||
if index == @name_index
|
||||
|
||||
# check if row (inventory) already exists
|
||||
existing_row = RepositoryRow.includes(repository_cells: :value).find_by(id: incoming_row[0].to_s.gsub(RepositoryRow::ID_PREFIX, ''))
|
||||
|
||||
# if it doesn't exist create it
|
||||
unless existing_row
|
||||
new_row =
|
||||
RepositoryRow.new(name: try_decimal_to_string(value),
|
||||
repository: @repository,
|
||||
created_by: @user,
|
||||
last_modified_by: @user)
|
||||
unless new_row.valid?
|
||||
errors = true
|
||||
break
|
||||
end
|
||||
new_full_row[:repository_row] = new_row
|
||||
next
|
||||
end
|
||||
|
||||
# if it's a preview always add the existing row
|
||||
if preview
|
||||
new_full_row[:repository_row] = existing_row
|
||||
|
||||
# otherwise add according to criteria
|
||||
else
|
||||
# if it does exist but shouldn't be edited, error out and break
|
||||
if existing_row && (can_edit_existing_items == false)
|
||||
errors = true
|
||||
break
|
||||
end
|
||||
|
||||
# if it does exist and should be edited, update the existing row
|
||||
if existing_row && (can_edit_existing_items == true)
|
||||
# update the existing row with incoming row data
|
||||
new_full_row[:repository_row] = existing_row
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
next unless @columns[index]
|
||||
new_full_row[index] = value
|
||||
end
|
||||
|
||||
if new_full_row[:repository_row].present?
|
||||
full_row_import_batch << new_full_row
|
||||
batch_counter += 1
|
||||
end
|
||||
|
||||
next if batch_counter < IMPORT_BATCH_SIZE
|
||||
|
||||
# import_batch_to_database(full_row_import_batch, can_edit_existing_items, should_overwrite_with_empty_cells, preview: preview)
|
||||
imported_rows += import_batch_to_database(full_row_import_batch, can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
full_row_import_batch = []
|
||||
batch_counter = 0
|
||||
end
|
||||
|
||||
# Import of the remaining rows
|
||||
imported_rows += import_batch_to_database(full_row_import_batch, can_edit_existing_items, should_overwrite_with_empty_cells, preview) if full_row_import_batch.any?
|
||||
|
||||
full_row_import_batch
|
||||
def handle_invalid_cell_value(value, cell_value)
|
||||
if value.present? && cell_value.nil?
|
||||
@errors << 'Incorrect data format'
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
|
||||
if errors
|
||||
return { status: :error,
|
||||
nr_of_added: @new_rows_added,
|
||||
total_nr: @total_new_rows }
|
||||
end
|
||||
changes = ActiveModelSerializers::SerializableResource.new(
|
||||
imported_rows,
|
||||
each_serializer: RepositoryRowSerializer,
|
||||
include: [:repository_cells]
|
||||
).as_json
|
||||
|
||||
{ status: :ok, nr_of_added: @new_rows_added, total_nr: @total_new_rows, changes: changes, import_date: I18n.l(Date.today, format: :full_date) }
|
||||
end
|
||||
|
||||
def import_batch_to_database(full_row_import_batch, can_edit_existing_items, should_overwrite_with_empty_cells, preview)
|
||||
skipped_rows = []
|
||||
def import_rows!
|
||||
checked_rows = []
|
||||
duplicate_ids = SpreadsheetParser.duplicate_ids(@sheet)
|
||||
|
||||
full_row_import_batch.map do |full_row|
|
||||
# skip archived rows and rows that belong to other repositories
|
||||
if full_row[:repository_row].archived || full_row[:repository_row].repository_id != @repository.id
|
||||
skipped_rows << full_row[:repository_row]
|
||||
@rows.each do |row|
|
||||
next if row.blank?
|
||||
|
||||
unless @header_skipped
|
||||
@header_skipped = true
|
||||
next
|
||||
end
|
||||
|
||||
full_row[:repository_row].save!(validate: false)
|
||||
@new_rows_added += 1
|
||||
incoming_row = SpreadsheetParser.parse_row(row, @sheet, date_format: @user.settings['date_format'])
|
||||
next if incoming_row.compact.blank?
|
||||
|
||||
full_row.reject { |k| k == :repository_row }.each do |index, value|
|
||||
column = @columns[index]
|
||||
value = try_decimal_to_string(value) unless column.repository_number_value?
|
||||
next if value.nil?
|
||||
@total_new_rows += 1
|
||||
|
||||
cell_value_attributes = {
|
||||
created_by: @user,
|
||||
last_modified_by: @user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: full_row[:repository_row],
|
||||
repository_column: column,
|
||||
importing: true
|
||||
}
|
||||
}
|
||||
if @id_index
|
||||
id = incoming_row[@id_index].to_s.gsub(RepositoryRow::ID_PREFIX, '')
|
||||
|
||||
cell_value = column.data_type.constantize.import_from_text(
|
||||
value,
|
||||
cell_value_attributes,
|
||||
@user.as_json(root: true, only: :settings).deep_symbolize_keys
|
||||
)
|
||||
if id.present?
|
||||
existing_row = @repository.repository_rows.includes(repository_cells: :value).find_by(id: id)
|
||||
|
||||
existing_cell = full_row[:repository_row].repository_cells.find { |c| c.repository_column_id == column.id }
|
||||
|
||||
next if cell_value.nil? && existing_cell.nil?
|
||||
|
||||
if existing_cell
|
||||
# existing_cell present && !can_edit_existing_items
|
||||
next if can_edit_existing_items == false
|
||||
|
||||
# existing_cell present && can_edit_existing_items
|
||||
if can_edit_existing_items == true
|
||||
# if incoming cell is not empty
|
||||
case cell_value
|
||||
|
||||
when RepositoryStockValue
|
||||
existing_cell.value.update_data!(cell_value, @user, preview: preview) unless cell_value.nil?
|
||||
|
||||
when RepositoryListValue
|
||||
repository_list_item_id = cell_value[:repository_list_item_id]
|
||||
existing_cell.value.update_data!(repository_list_item_id, @user, preview: preview) unless cell_value.nil?
|
||||
|
||||
when RepositoryStatusValue
|
||||
repository_status_item_id = cell_value[:repository_status_item_id]
|
||||
existing_cell.value.update_data!(repository_status_item_id, @user, preview: preview) unless cell_value.nil?
|
||||
|
||||
else
|
||||
sanitized_cell_value_data = sanitize_cell_value_data(cell_value.data)
|
||||
existing_cell.value.update_data!(sanitized_cell_value_data, @user, preview: preview) unless cell_value.nil?
|
||||
end
|
||||
|
||||
# if incoming cell is empty && should_overwrite_with_empty_cells
|
||||
existing_cell.value.destroy! if cell_value.nil? && should_overwrite_with_empty_cells == true
|
||||
|
||||
# if incoming cell is empty && !should_overwrite_with_empty_cells
|
||||
next if cell_value.nil? && should_overwrite_with_empty_cells == false
|
||||
end
|
||||
else
|
||||
# no existing_cell. Create a new one.
|
||||
cell_value.repository_cell.value = cell_value
|
||||
cell_value.save!(validate: false)
|
||||
existing_row ||= @repository.repository_rows.new(
|
||||
id: SecureRandom.uuid,
|
||||
created_by: @user,
|
||||
last_modified_by: @user,
|
||||
import_status: 'invalid',
|
||||
import_message: I18n.t('repositories.import_records.steps.step3.status_message.not_exist', id: id.to_i)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
full_row[:repository_row]
|
||||
if existing_row.present?
|
||||
if !@can_edit_existing_items
|
||||
existing_row.import_status = 'unchanged'
|
||||
elsif existing_row.archived
|
||||
existing_row.import_status = 'archived'
|
||||
elsif duplicate_ids.include?(existing_row.id)
|
||||
existing_row.import_status = 'duplicated'
|
||||
end
|
||||
|
||||
if existing_row.import_status.present?
|
||||
checked_rows << existing_row if @preview
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
checked_rows << import_row(existing_row, incoming_row)
|
||||
end
|
||||
changes = ActiveModelSerializers::SerializableResource.new(
|
||||
checked_rows.compact,
|
||||
each_serializer: RepositoryRowImportSerializer,
|
||||
include: [:repository_cells]
|
||||
).as_json
|
||||
|
||||
p changes
|
||||
|
||||
{ status: :ok, nr_of_added: @new_rows_added, total_nr: @total_new_rows, changes: changes,
|
||||
import_date: I18n.l(Date.today, format: :full_date) }
|
||||
end
|
||||
|
||||
def import_row(repository_row, import_row)
|
||||
@repository.transaction do
|
||||
@errors = []
|
||||
@updated = false
|
||||
repository_row_name = try_decimal_to_string(import_row[@name_index])
|
||||
if repository_row.present?
|
||||
repository_row.name = repository_row_name
|
||||
else
|
||||
repository_row = RepositoryRow.new(name: repository_row_name,
|
||||
repository: @repository,
|
||||
created_by: @user,
|
||||
last_modified_by: @user,
|
||||
import_status: 'created')
|
||||
end
|
||||
|
||||
if @preview
|
||||
repository_row.validate
|
||||
repository_row.id = SecureRandom.uuid unless repository_row.id.present? # ID required for preview with serializer
|
||||
else
|
||||
repository_row.save!
|
||||
end
|
||||
@errors << repository_row.errors.full_messages.join(',') if repository_row.errors.present?
|
||||
|
||||
@updated = repository_row.changed?
|
||||
|
||||
@columns.each_with_index do |column, index|
|
||||
next if column.blank?
|
||||
|
||||
value = import_row[index]
|
||||
value = try_decimal_to_string(value) unless column.repository_number_value?
|
||||
|
||||
cell_value = if value.present?
|
||||
column.data_type.constantize.import_from_text(
|
||||
value,
|
||||
{
|
||||
created_by: @user,
|
||||
last_modified_by: @user,
|
||||
repository_cell_attributes: {
|
||||
repository_row: repository_row,
|
||||
repository_column: column,
|
||||
importing: true
|
||||
}
|
||||
},
|
||||
@user.as_json(root: true, only: :settings).deep_symbolize_keys
|
||||
)
|
||||
end
|
||||
next if handle_invalid_cell_value(value, cell_value)
|
||||
|
||||
existing_cell = repository_row.repository_cells.find { |c| c.repository_column_id == column.id }
|
||||
|
||||
existing_cell = if cell_value.nil?
|
||||
handle_nil_cell_value(existing_cell)
|
||||
else
|
||||
handle_existing_cell_value(existing_cell, cell_value, repository_row)
|
||||
end
|
||||
|
||||
@updated ||= existing_cell&.value&.changed?
|
||||
@errors << existing_cell.value.errors.full_messages.join(',') if existing_cell&.value&.errors.present?
|
||||
end
|
||||
repository_row.import_status = if @errors.present?
|
||||
'invalid'
|
||||
elsif repository_row.import_status == 'created'
|
||||
@new_rows_added += 1
|
||||
'created'
|
||||
elsif @updated
|
||||
@new_rows_added += 1
|
||||
'updated'
|
||||
else
|
||||
'unchanged'
|
||||
end
|
||||
repository_row.import_message = @errors.join(',').downcase if @errors.present?
|
||||
repository_row
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
|
||||
def handle_nil_cell_value(repository_cell)
|
||||
return unless repository_cell.present? && @should_overwrite_with_empty_cells
|
||||
|
||||
if @preview
|
||||
repository_cell.to_destroy = true
|
||||
@updated = true
|
||||
else
|
||||
repository_cell.value.destroy!
|
||||
end
|
||||
|
||||
repository_cell
|
||||
end
|
||||
|
||||
def handle_existing_cell_value(repository_cell, cell_value, repository_row)
|
||||
if repository_cell.present?
|
||||
case cell_value
|
||||
when RepositoryStockValue
|
||||
repository_cell.value.update_data!(cell_value, @user, preview: @preview)
|
||||
when RepositoryListValue
|
||||
repository_list_item_id = cell_value[:repository_list_item_id]
|
||||
repository_cell.value.update_data!(repository_list_item_id, @user, preview: @preview)
|
||||
when RepositoryStatusValue
|
||||
repository_status_item_id = cell_value[:repository_status_item_id]
|
||||
repository_cell.value.update_data!(repository_status_item_id, @user, preview: @preview)
|
||||
else
|
||||
sanitized_cell_value_data = sanitize_cell_value_data(cell_value.data)
|
||||
repository_cell.value.update_data!(sanitized_cell_value_data, @user, preview: @preview)
|
||||
end
|
||||
repository_cell
|
||||
else
|
||||
# Create new cell
|
||||
cell_value.repository_cell.value = cell_value
|
||||
repository_row.repository_cells << cell_value.repository_cell
|
||||
@preview ? cell_value.validate : cell_value.save!
|
||||
@updated ||= true
|
||||
cell_value.repository_cell
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -117,6 +117,7 @@
|
|||
data-protocol-url="<%= protocol_my_module_path(@my_module) %>"
|
||||
data-date-format="<%= datetime_picker_format_date_only %>"
|
||||
data-user-utc-offset="<%= ActiveSupport::TimeZone.find_tzinfo(current_user.time_zone).utc_offset %>"
|
||||
data-e2e="e2e-CO-task-protocol"
|
||||
>
|
||||
<protocol-container
|
||||
:protocol-url="protocolUrl"
|
||||
|
|
|
@ -1,29 +1,45 @@
|
|||
<%= form_for :protocol, url: team_import_external_protocol_path(team_id: current_team.id),
|
||||
method: :post, data: { remote: true } do |f|%>
|
||||
<div class="general-error has-error">
|
||||
<div class="general-error has-error" data-e2e="e2e-TX-protocolTemplates-previewProtocolsIo-error">
|
||||
<span class="has-error help-block"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group sci-input-container">
|
||||
<%= f.label :name, t('protocols.import_export.import_modal.name_label') %>
|
||||
<%= f.text_field :name, class: 'form-control sci-input-field', value: protocol.name %>
|
||||
<%= f.label :name,
|
||||
t('protocols.import_export.import_modal.name_label'),
|
||||
:"data-e2e" => "e2e-TX-protocolTemplates-previewProtocolsIo-nameInput" %>
|
||||
<%= f.text_field :name,
|
||||
class: 'form-control sci-input-field',
|
||||
value: protocol.name,
|
||||
:"data-e2e" => "e2e-IF-protocolTemplates-previewProtocolsIo-nameInput" %>
|
||||
<span class="help-block"></span>
|
||||
</div>
|
||||
|
||||
<div class="form-group sci-input-container">
|
||||
<%= f.label :authors, t('protocols.import_export.import_modal.authors_label') %>
|
||||
<%= f.text_field :authors, class: 'form-control sci-input-field', value: protocol.authors %>
|
||||
<%= f.label :authors,
|
||||
t('protocols.import_export.import_modal.authors_label'),
|
||||
:"data-e2e" => "e2e-TX-protocolTemplates-previewProtocolsIo-authorsInput" %>
|
||||
<%= f.text_field :authors,
|
||||
class: 'form-control sci-input-field',
|
||||
value: protocol.authors,
|
||||
:"data-e2e" => "e2e-IF-protocolTemplates-previewProtocolsIo-authorsInput" %>
|
||||
</div>
|
||||
|
||||
<div class="import-protocol-preview-description">
|
||||
<div class="import-protocol-preview-description" data-e2e="e2e-TX-protocolTemplates-previewProtocolsIo-description">
|
||||
<%= custom_auto_link(protocol.description, simple_format: false, team: current_team) %>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4">
|
||||
<div class="form-group">
|
||||
<%= f.label :published_on_label, t('protocols.import_export.import_modal.published_on_label')%>
|
||||
<%= f.text_field :published_on_label, value: I18n.l(protocol.published_on, format: :full), class: 'form-control', disabled: true %>
|
||||
<%= f.label :published_on_label,
|
||||
t('protocols.import_export.import_modal.published_on_label'),
|
||||
:"data-e2e" => "e2e-TX-protocolTemplates-previewProtocolsIo-publishedOnLabel" %>
|
||||
<%= f.text_field :published_on_label,
|
||||
value: I18n.l(protocol.published_on, format: :full),
|
||||
class: 'form-control',
|
||||
disabled: true,
|
||||
:'data-e2e' => "e2e-TX-protocolTemplates-previewProtocolsIo-publishedOn" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -39,11 +55,11 @@
|
|||
|
||||
<div data-role="steps-container">
|
||||
<div class="row">
|
||||
<div class="col-xs-8">
|
||||
<div class="col-xs-8" data-e2e="e2e-TX-protocolTemplates-previewProtocolsIo-protocolSteps">
|
||||
<h2><%= t("protocols.steps.subtitle") %></h2>
|
||||
</div>
|
||||
</div>
|
||||
<div id="steps">
|
||||
<div id="steps" data-e2e="e2e-CO-protocolTemplates-previewProtocolsIo-protocolSteps">
|
||||
<% protocol.steps.sort_by{ |s| s.position }.each do |step| %>
|
||||
<%= render partial: "steps/step", locals: { step: step, steps_assets: steps_assets, preview: true, import: true } %>
|
||||
<% end %>
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
<div class="footer">
|
||||
<div class="left-section">
|
||||
<div class="default-role-container">
|
||||
<div class="sci-checkbox-container">
|
||||
<div class="sci-checkbox-container" data-e2e="e2e-CB-protocolTemplates-previewProtocolsIo-grantAccess">
|
||||
<%= check_box_tag "visibility", "visible", false, class: "sci-checkbox" %>
|
||||
<span class="sci-checkbox-label"></span>
|
||||
</div>
|
||||
<div class="default-role-description">
|
||||
<div class="default-role-description" data-e2e="e2e-TX-protocolTemplates-previewProtocolsIo-grantAccess">
|
||||
<%= t("protocols.new_protocol_modal.access_label") %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden" id="roleSelectWrapper">
|
||||
<div class="hidden" id="roleSelectWrapper" data-e2e="e2e-DD-protocolTemplates-previewProtocolsIo-userRole">
|
||||
<div class="sci-input-container">
|
||||
<%= label_tag :default_public_user_role_id, t("protocols.new_protocol_modal.role_label") %>
|
||||
<% default_role = UserRole.find_by(name: I18n.t('user_roles.predefined.viewer')).id %>
|
||||
|
@ -19,7 +19,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="right-section">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%=t "general.cancel" %></button>
|
||||
<button type="button" class="btn btn-primary" data-action="import_protocol" data-import_type="in_repository_draft"><%=t "protocols.import_export.import_modal.import_protocols_label" %></button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"
|
||||
data-e2e="e2e-BT-protocolTemplates-previewProtocolsIo-cancel">
|
||||
<%=t "general.cancel" %>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary" data-action="import_protocol"
|
||||
data-import_type="in_repository_draft" data-e2e="e2e-BT-protocolTemplates-previewProtocolsIo-import">
|
||||
<%=t "protocols.import_export.import_modal.import_protocols_label" %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,61 +1,101 @@
|
|||
<div id="import-protocol-modal" class="modal fade" role="dialog">
|
||||
<div id="import-protocol-modal" class="modal fade" role="dialog" data-e2e="e2e-MD-protocolTemplates-importEln">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title" data-role="header-import"><%= t("protocols.import_export.import_modal.title_import") %></h4>
|
||||
<h4 class="modal-title" data-role="header-import-into-protocol"><%= t("protocols.import_export.import_modal.title_import_into_protocol") %></h4>
|
||||
<button type="button" class="close" data-dismiss="modal" data-e2e="e2e-BT-protocolTemplates-importEln-close">
|
||||
×
|
||||
</button>
|
||||
<h4 class="modal-title" data-role="header-import" data-e2e="e2e-TX-protocolTemplates-importEln-title">
|
||||
<%= t("protocols.import_export.import_modal.title_import") %>
|
||||
</h4>
|
||||
<h4 class="modal-title" data-role="header-import-into-protocol">
|
||||
<%= t("protocols.import_export.import_modal.title_import_into_protocol") %>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<!-- Warning message -->
|
||||
<div data-role="import-message" style="margin-bottom: 15px;">
|
||||
<div data-role="import-message"
|
||||
style="margin-bottom: 15px;"
|
||||
data-e2e="e2e-TX-protocolTemplates-importEln-warning">
|
||||
<b><%= t("protocols.import_export.import_modal.import_into_protocol_message") %></b>
|
||||
<br />
|
||||
</div>
|
||||
|
||||
<!-- General protocol info -->
|
||||
<div class="form-group sci-input-container">
|
||||
<label for="import_protocol_name"><%= t("protocols.import_export.import_modal.name_label") %></label>
|
||||
<input type="text" class="form-control sci-input-field" id="import_protocol_name">
|
||||
<label for="import_protocol_name" data-e2e="e2e-TX-protocolTemplates-importEln-nameInput">
|
||||
<%= t("protocols.import_export.import_modal.name_label") %>
|
||||
</label>
|
||||
<input type="text"
|
||||
class="form-control sci-input-field"
|
||||
id="import_protocol_name"
|
||||
data-e2e="e2e-IF-protocolTemplates-importEln-nameInput">
|
||||
</div>
|
||||
<div class="form-group sci-input-container">
|
||||
<label for="protocol_authors">
|
||||
<label for="protocol_authors" data-e2e="e2e-TX-protocolTemplates-importEln-authorsInput">
|
||||
<span class="sn-icon sn-icon-user-menu"></span> <%= t("protocols.import_export.import_modal.authors_label") %>
|
||||
</label>
|
||||
<input type="text" class="form-control sci-input-field" id="protocol_authors">
|
||||
<input type="text"
|
||||
class="form-control sci-input-field"
|
||||
id="protocol_authors"
|
||||
data-e2e="e2e-IF-protocolTemplates-importEln-authorsInput">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="import_protocol_description"><%= t("protocols.import_export.import_modal.description_label") %></label>
|
||||
<div id="import_protocol_description" class="overflow-auto" rows="2"></div>
|
||||
<label for="import_protocol_description" data-e2e="e2e-TX-protocolTemplates-importEln-descriptionLabel">
|
||||
<%= t("protocols.import_export.import_modal.description_label") %>
|
||||
</label>
|
||||
<div id="import_protocol_description"
|
||||
class="overflow-auto"
|
||||
rows="2"
|
||||
data-e2e="e2e-TX-protocolTemplates-importEln-description">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
<label for="protocol_created_at"><%= t("protocols.import_export.import_modal.created_at_label") %></label>
|
||||
<input type="text" class="form-control" id="protocol_created_at" disabled>
|
||||
<label for="protocol_created_at" data-e2e="e2e-TX-protocolTemplates-importEln-createdAtLabel">
|
||||
<%= t("protocols.import_export.import_modal.created_at_label") %>
|
||||
</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="protocol_created_at"
|
||||
disabled
|
||||
data-e2e="e2e-TX-protocolTemplates-importEln-createdAt">
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<label for="protocol_updated_at"><%= t("protocols.import_export.import_modal.updated_at_label") %></label>
|
||||
<input type="text" class="form-control" id="protocol_updated_at" disabled>
|
||||
<label for="protocol_updated_at" data-e2e="e2e-TX-protocolTemplates-importEln-updatedAtLabel">
|
||||
<%= t("protocols.import_export.import_modal.updated_at_label") %>
|
||||
</label>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="protocol_updated_at"
|
||||
disabled
|
||||
data-e2e="e2e-TX-protocolTemplates-importEln-updatedAt">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview title -->
|
||||
<div>
|
||||
<h2 style="display: inline;"><%= t("protocols.import_export.import_modal.preview_title") %></h2>
|
||||
<h2 style="display: inline;" data-e2e="e2e-TX-protocolTemplates-importEln-previewTitle">
|
||||
<%= t("protocols.import_export.import_modal.preview_title") %>
|
||||
</h2>
|
||||
<h3 style="display: none;" data-role="title-position"></h3>
|
||||
</div>
|
||||
|
||||
<!-- Preview scroller -->
|
||||
<div>
|
||||
<div class="import-protocols-modal-preview-container" data-role="preview-container">
|
||||
<div class="import-protocols-modal-preview-container"
|
||||
data-role="preview-container"
|
||||
data-e2e="e2e-CO-protocolTemplates-importEln-preview">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div data-role="multiple-protocols-buttons">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%= t("general.cancel") %></button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
<%= t("general.cancel") %>
|
||||
</button>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="#" class="btn btn-secondary" data-action="jump-to-first-protocol"><i class="fas fa-fast-backward"></i></a>
|
||||
<a href="#" class="btn btn-secondary" data-action="jump-to-previous-protocol"><i class="fas fa-backward"></i></a>
|
||||
|
@ -64,18 +104,34 @@
|
|||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<div data-role="import-all">
|
||||
<button type="submit" class="btn btn-success" data-action="import-current"><%= t("protocols.import_export.import_modal.import_current") %></button>
|
||||
<button type="submit" class="btn btn-success" data-action="import-all"><%= t("protocols.import_export.import_modal.import_all") %></button>
|
||||
<button type="submit" class="btn btn-success" data-action="import-current">
|
||||
<%= t("protocols.import_export.import_modal.import_current") %>
|
||||
</button>
|
||||
<button type="submit" class="btn btn-success" data-action="import-all">
|
||||
<%= t("protocols.import_export.import_modal.import_all") %>
|
||||
</button>
|
||||
</div>
|
||||
<div data-role="import-single">
|
||||
<button type="submit" class="btn btn-success" data-action="import-current"><%= t("protocols.import_export.import_modal.import") %></button>
|
||||
<button type="submit" class="btn btn-success" data-action="import-current">
|
||||
<%= t("protocols.import_export.import_modal.import") %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-role="single-protocol-buttons">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal"><%= t("general.cancel") %></button>
|
||||
<button type="button"
|
||||
class="btn btn-secondary"
|
||||
data-dismiss="modal"
|
||||
data-e2e="e2e-BT-protocolTemplates-importEln-cancel">
|
||||
<%= t("general.cancel") %>
|
||||
</button>
|
||||
<div class="btn-group" role="group">
|
||||
<button type="submit" class="btn btn-success" data-action="import-current"><%= t("protocols.import_export.import_modal.import") %></button>
|
||||
<button type="submit"
|
||||
class="btn btn-success"
|
||||
data-action="import-current"
|
||||
data-e2e="e2e-BT-protocolTemplates-importEln-load">
|
||||
<%= t("protocols.import_export.import_modal.import") %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
<div class="content-pane flexible protocols-index <%= @type %>">
|
||||
<div class="content-header sticky-header">
|
||||
<div class="title-row">
|
||||
<div class="title-row" data-e2e="e2e-TX-protocolTemplates-title">
|
||||
<% if templates_view_mode_archived?(type: @type) %>
|
||||
<h1>
|
||||
<span><%= t('labels.archived')%></span>
|
||||
|
@ -24,7 +24,7 @@
|
|||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="protocols-container">
|
||||
<div class="protocols-container" data-e2e="e2e-CO-protocolTemplates">
|
||||
<div id="ProtocolsTable" class="fixed-content-body">
|
||||
<protocols-table
|
||||
ref="table"
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
<div class="modal" id="protocol-preview-modal" tabindex="-1" role="dialog" aria-labelledby="protocol-preview-modal-label">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-content" data-e2e="e2e-MD-protocolTemplates-previewProtocolsIo">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<h4 class="modal-title" id="protocol-preview-modal-label">
|
||||
<button type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
data-e2e="e2e-BT-protocolTemplates-previewProtocolsIo-close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title"
|
||||
id="protocol-preview-modal-label"
|
||||
data-e2e="e2e-TX-protocolTemplates-previewProtocolsIo-title">
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body"></div>
|
||||
<div class="modal-footer"></div>
|
||||
<div class="modal-body" data-e2e="e2e-CO-protocolTemplates-previewProtocolsIo-body"></div>
|
||||
<div class="modal-footer" data-e2e="e2e-CO-protocolTemplates-previewProtocolsIo-footer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="modal" id="protocolsioModal" data-url="<%= protocolsio_protocols_path %>" tabindex="-1" role="dialog" aria-labelledby="protocolsio-modal-label">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content"></div>
|
||||
<div class="modal-content" data-e2e="e2e-MD-protocolTemplates-importProtocolsIo"></div>
|
||||
</div>
|
||||
</div>
|
||||
<%= javascript_include_tag "protocols/steps" %>
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><i class="sn-icon sn-icon-close"></i></button>
|
||||
<h4 class="modal-title" id="publish-results-modal-label">
|
||||
<button type="button"
|
||||
class="close"
|
||||
data-dismiss="modal"
|
||||
aria-label="Close"
|
||||
data-e2e="e2e-BT-protocolTemplates-importProtocolsIo-close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title" id="publish-results-modal-label" data-e2e="e2e-TX-protocolTemplates-importProtocolsIo-title">
|
||||
<%= t('protocols.index.protocolsio.title') %>
|
||||
</h4>
|
||||
</div>
|
||||
|
@ -20,6 +26,7 @@
|
|||
<input class='sci-input-field'
|
||||
type='text'
|
||||
name='key'
|
||||
data-e2e='e2e-IF-protocolTemplates-importProtocolsIo-search'
|
||||
placeholder="<%= t('protocols.index.protocolsio.search_bar_placeholder') %>" />
|
||||
<i class='sn-icon sn-icon-search'></i>
|
||||
</div>
|
||||
|
@ -27,7 +34,13 @@
|
|||
|
||||
<div class='protocol-sort'>
|
||||
<div class="dropdown sort-menu" title="<%= t("general.sort.title") %>">
|
||||
<button class="btn btn-light btn-black icon-btn" type="button" id="sortMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<button class="btn btn-light btn-black icon-btn"
|
||||
type="button"
|
||||
id="sortMenu"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="true"
|
||||
data-e2e="e2e-DD-protocolTemplates-importProtocolsIo-sort">
|
||||
<span><i class="sn-icon sn-icon-sort-down"></i></span>
|
||||
</button>
|
||||
<ul id="sortMenuDropdown" class="dropdown-menu sort-projects-menu dropdown-menu-right" aria-labelledby="sortMenu">
|
||||
|
@ -43,33 +56,39 @@
|
|||
</div>
|
||||
<% end %>
|
||||
<div class='protocol-list-side-panel'>
|
||||
<div class='row empty-text'>
|
||||
<div class='row empty-text' data-e2e="e2e-TX-protocolTemplates-importProtocolsIo-results-empty">
|
||||
<%= t('protocols.index.protocolsio.list_panel.empty_text') %>
|
||||
</div>
|
||||
|
||||
<div class='list-wrapper perfect-scrollbar'></div>
|
||||
<div class='list-wrapper perfect-scrollbar' data-e2e="e2e-CO-protocolTemplates-importProtocolsIo-results">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='protocol-preview-panel'>
|
||||
<div class='empty-preview-panel'>
|
||||
|
||||
<div class='row'>
|
||||
<div class='text-rows protocol-preview-text'>
|
||||
<div class='text-rows protocol-preview-text'
|
||||
data-e2e="e2e-TX-protocolTemplates-importProtocolsIo-previewEmpty-title">
|
||||
<%= t('protocols.index.protocolsio.preview_panel.empty_title') %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='row'>
|
||||
<div class='text-separator'> <hr> </div>
|
||||
<div class='text-separator' data-e2e="e2e-EL-protocolTemplates-importProtocolsIo-previewEmpty-separator">
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='row'>
|
||||
<div class='text-rows protocol-preview-subtext'>
|
||||
<div class='text-rows protocol-preview-subtext'
|
||||
data-e2e="e2e-TX-protocolTemplates-importProtocolsIo-previewEmpty-subText">
|
||||
<%= t('protocols.index.protocolsio.preview_panel.empty_subtext') %>
|
||||
</div>
|
||||
</div>
|
||||
<div class='row-bottom'>
|
||||
<div class='text-rows protocol-preview-subtext'>
|
||||
<div class='text-rows protocol-preview-subtext'
|
||||
data-e2e="e2e-TX-protocolTemplates-importProtocolsIo-previewEmpty-poweredBy">
|
||||
<%= t('protocols.index.protocolsio.preview_panel.powered_by') %>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -77,7 +96,7 @@
|
|||
|
||||
<div class='full-preview-panel' style='display: none;'>
|
||||
<div class='row preview-banner'>
|
||||
<div class='col-md-6 txt-holder'>
|
||||
<div class='col-md-6 txt-holder' data-e2e="e2e-TX-protocolTemplates-importProtocolsIo-preview-title">
|
||||
<span>
|
||||
<b><%= t('protocols.index.protocolsio.preview_panel.banner_text') %></b>
|
||||
</span>
|
||||
|
@ -85,7 +104,7 @@
|
|||
<div class='col-md-6 btn-holder'>
|
||||
</div>
|
||||
</div>
|
||||
<div class='preview-holder perfect-scrollbar'>
|
||||
<div class='preview-holder perfect-scrollbar' data-e2e="e2e-CO-protocolTemplates-importProtocolsIo-preview">
|
||||
<iframe scrolling="no" class='preview-iframe'></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -94,7 +113,17 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" data-dismiss="modal" class="btn btn-secondary"><%=t('general.cancel') %></button>
|
||||
<button type="button" class="btn btn-primary convert-protocol" disabled><%= t('protocols.index.protocolsio.convert') %></button>
|
||||
<button type="button"
|
||||
data-dismiss="modal"
|
||||
class="btn btn-secondary"
|
||||
data-e2e="e2e-BT-protocolTemplates-importProtocolsIo-cancel">
|
||||
<%=t('general.cancel') %>
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-primary convert-protocol"
|
||||
disabled
|
||||
data-e2e="e2e-BT-protocolTemplates-importProtocolsIo-convert">
|
||||
<%= t('protocols.index.protocolsio.convert') %>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<div class="content-pane protocols-show flexible with-grey-background pb-4" >
|
||||
<div class="content-header sticky-header">
|
||||
<div class="title-row">
|
||||
<h1>
|
||||
<h1 data-e2e="e2e-TX-protocolTemplates-protocol-title">
|
||||
<% if @inline_editable_title_config.present? %>
|
||||
<%= render partial: "shared/inline_editing",
|
||||
locals: {
|
||||
|
@ -33,6 +33,7 @@
|
|||
data-protocol-url="<%= protocol_path(@protocol) %>"
|
||||
data-date-format="<%= datetime_picker_format_date_only %>"
|
||||
data-user-utc-offset="<%= ActiveSupport::TimeZone.find_tzinfo(current_user.time_zone).utc_offset %>"
|
||||
data-e2e="e2e-CO-protocolTemplates-protocol"
|
||||
>
|
||||
<protocol-container
|
||||
:protocol-url="protocolUrl"
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
<% if error.present? %>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<div><%= error_title %></div>
|
||||
<br>
|
||||
<%= error %>
|
||||
</div>
|
||||
<% end %>
|
|
@ -77,6 +77,8 @@ en:
|
|||
filter_create_new: "Create"
|
||||
cancel: "Cancel"
|
||||
create: "Create"
|
||||
new_project: "New \"%{name}\" Project"
|
||||
new_experiment: "New \"%{name}\" Experiment"
|
||||
recent_work:
|
||||
title: "Recent work"
|
||||
no_results:
|
||||
|
|
|
@ -2197,38 +2197,40 @@ en:
|
|||
list_row: "Row %{row}"
|
||||
list_error: "%{key}: %{val}"
|
||||
import_records:
|
||||
update_inventory: 'Update inventory'
|
||||
update_inventory: 'Import items'
|
||||
steps:
|
||||
step1:
|
||||
id: 'step1'
|
||||
icon: 'sn-icon-open'
|
||||
label: 'Step 1'
|
||||
title: 'Update inventory'
|
||||
subtitle: 'To add or edit items, export the inventory and reimport edited inventory.'
|
||||
title: 'Import items'
|
||||
subtitle: 'Inventory import allows for adding new items or updating existing data. Ensure the imported file contains column header names. For easy import, export the full inventory with existing items or only the inventory columns template.'
|
||||
helpText: 'Help'
|
||||
exportTitle: 'Export'
|
||||
exportFullInvBtnText: 'Export full inventory'
|
||||
exportEmptyInvBtnText: 'Export empty inventory'
|
||||
exportFullInvBtnText: 'Export all items'
|
||||
exportEmptyInvBtnText: 'Download inventory template'
|
||||
importTitle: 'Import'
|
||||
importBtnText: 'Import'
|
||||
cancelBtnText: 'Cancel'
|
||||
dragAndDropSupportingText: '.XLSX, .XLS or .CSV file'
|
||||
dragAndDropSupportingText: '.xlsx, .xls, .csv or .txt file'
|
||||
step2:
|
||||
id: 'step2'
|
||||
icon: 'sn-icon-open'
|
||||
label: 'Step 2'
|
||||
title: 'Mapping data'
|
||||
subtitle: 'Match your imported columns with the columns in the SciNote inventory.'
|
||||
subtitle: 'Match your imported file columns with the existing columns in the inventory to finalize the item import.'
|
||||
selectNamePropertyError: 'Select Name attribute field to import your items.'
|
||||
autoMappingText: 'Auto-mapping'
|
||||
autoMappingTooltip: 'When auto-mapping is selected, SciNote automatically connects columns with matching names. When not selected, manual mapping of each column is required.'
|
||||
updateEmptyCellsText: 'Update empty cells'
|
||||
onlyAddNewItemsText: 'Only add new items'
|
||||
importedFileText: 'Imported file:'
|
||||
cancelBtnText: 'Cancel'
|
||||
confirmBtnText: 'Confirm'
|
||||
importedIgnoredSection: '<b>%{imported}</b> columns to <b>import.</b> <b>%{ignored}</b> columns <b>ignored</b>.'
|
||||
importedIgnoredSection: '<b>%{imported}</b> columns to <b>import.</b> <b>%{ignored}</b> columns <b>ignored.</b>'
|
||||
computedDropdownOptions:
|
||||
name: 'Name'
|
||||
id: 'ID'
|
||||
RepositoryTextValue: 'Text'
|
||||
RepositoryNumberValue: 'Number'
|
||||
RepositoryAssetValue: 'File'
|
||||
|
@ -2245,12 +2247,9 @@ en:
|
|||
placeholders:
|
||||
matchNotFound: 'Match not found'
|
||||
doNotImport: 'Do not import'
|
||||
defaultColumnTitle: 'Default column. Mapped as identifier.'
|
||||
matchNotFoundColumnTitle: 'Match not found.'
|
||||
userDefinedColumnTitle: 'Column name does not match. Column will be imported as '
|
||||
matchNotFoundColumnTitle: 'Column match not found.'
|
||||
importedColumnTitle: 'Column will be imported.'
|
||||
doNotImportColumnTitle: 'Column will not import.'
|
||||
importAsNewColumn: 'Import as new column'
|
||||
doNotImportColumnTitle: 'Column will not be imported.'
|
||||
newColumnType:
|
||||
text: 'Text'
|
||||
list: 'Dropdown'
|
||||
|
@ -2271,18 +2270,26 @@ en:
|
|||
exampleData: 'Example data'
|
||||
step3:
|
||||
title: 'Import preview'
|
||||
subtitle: 'This is a preview of items you are importing/updating to the %{inventory}. The import can still be canceled.'
|
||||
updated_items: 'Updated<br>items'
|
||||
new_items: 'New<br>items'
|
||||
unchanged_items: 'Unchanged<br>items'
|
||||
duplicated_items: 'Duplicated<br>items'
|
||||
invalid_items: 'Invalid<br>items'
|
||||
invalid_cells: 'Invalid<br>cells'
|
||||
code: 'Code'
|
||||
subtitle: 'This preview shows changes to the %{inventory} inventory resulting from this import. Values that will be updated are marked in green and any errors in red. Status of import provides further details, and the item import can still be canceled at this stage.'
|
||||
updated_items: 'Updated'
|
||||
new_items: 'New'
|
||||
unchanged_items: 'Unchanged'
|
||||
duplicated_items: 'Duplicated'
|
||||
invalid_items: 'Invalid'
|
||||
archived_items: 'Archived'
|
||||
code: 'Item ID'
|
||||
name: 'Name'
|
||||
status: 'Status'
|
||||
cancel: 'Cancel import'
|
||||
confirm: 'Confirm'
|
||||
status_message:
|
||||
created: 'new item'
|
||||
updated: 'updated'
|
||||
unchanged: 'unchanged'
|
||||
not_exist: "item ID IT%{id} doesn't exist in this inventory"
|
||||
archived: 'archived'
|
||||
invalid: 'invalid item'
|
||||
duplicated: 'item ID has duplicates in the imported file'
|
||||
step4:
|
||||
title: 'Success report'
|
||||
subtitle: '%{inventory} was successfully updated.'
|
||||
|
@ -2291,28 +2298,28 @@ en:
|
|||
download_report: 'Download success report'
|
||||
close: 'Close'
|
||||
info_sidebar:
|
||||
title: 'Guide for updating the inventory'
|
||||
title: 'Item import guide'
|
||||
elements:
|
||||
element0:
|
||||
id: 'el0'
|
||||
icon: 'sn-icon-export'
|
||||
label: 'Export inventory'
|
||||
subtext: "Before making edits, we advise you to export the latest inventory information. If you're only adding new items, consider exporting empty inventory."
|
||||
subtext: "Before making changes, we advise you to first export the current inventory item information. If you're only adding in new items, exporting the inventory template is recommended."
|
||||
element1:
|
||||
id: 'el1'
|
||||
icon: 'sn-icon-edit'
|
||||
label: 'Edit your data'
|
||||
subtext: 'Make sure to include header names in first row, followed by item data.'
|
||||
subtext: 'Structure your import data”. And the text updated to: “Make sure to include header names in the first row of the import file, followed by item data.'
|
||||
element2:
|
||||
id: 'el2'
|
||||
icon: 'sn-icon-import'
|
||||
label: 'Import new or update items'
|
||||
subtext: 'Upload your data using .xlsx, .csv or .txt files.'
|
||||
label: 'Upload your file'
|
||||
subtext: 'Upload your data using .xlsx, .csv, or .txt files to import new items or update existing item data.'
|
||||
element3:
|
||||
id: 'el3'
|
||||
icon: 'sn-icon-tables'
|
||||
label: 'Merge your data'
|
||||
subtext: 'Complete the process by merging the columns you want to update.'
|
||||
label: 'Map and finalize your data'
|
||||
subtext: 'Complete the item import process by mapping uploaded file data with the columns you want to update in the inventory.'
|
||||
element4:
|
||||
id: 'el4'
|
||||
icon: 'sn-icon-open'
|
||||
|
@ -2330,7 +2337,7 @@ en:
|
|||
import: 'Import'
|
||||
no_header_name: 'No column name'
|
||||
success_flash: "%{number_of_rows} of %{total_nr} new item(s) successfully imported."
|
||||
partial_success_flash: "%{nr} of %{total_nr} successfully imported. Other rows contained errors."
|
||||
partial_success_flash: "%{nr} of %{total_nr} items successfully imported."
|
||||
error_message:
|
||||
items_limit: "The imported file contains too many rows. Max %{items_size} items allowed to upload at once."
|
||||
importing_duplicates: "Items with duplicates detected: %{duplicate_ids}. These will be ignored on import."
|
||||
|
@ -2341,6 +2348,7 @@ en:
|
|||
duplicated_values: "Two or more columns have the same mapping."
|
||||
errors_list_title: "Items were not imported because one or more errors were found:"
|
||||
no_repository_name: "Item name is required!"
|
||||
mapping_error: "Column mappings are required"
|
||||
edit_record: "Edit"
|
||||
assign_record: "Assign to task"
|
||||
copy_record: "Duplicate"
|
||||
|
@ -2424,6 +2432,7 @@ en:
|
|||
no_records_selected_flash: "There were no selected items."
|
||||
no_deleted_records_flash: "No items were deleted. %{other_records_number} of the selected items were created by other users and were not deleted."
|
||||
default_column: 'Name'
|
||||
id_column: 'Item ID'
|
||||
copy_records_report: "%{number} item(s) successfully copied."
|
||||
archive_inventories:
|
||||
success_flash: "Inventories were successfully archived!"
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
class AddDiscardedAtToResults < ActiveRecord::Migration[7.0]
|
||||
def change
|
||||
add_column :results, :discarded_at, :datetime
|
||||
add_index :results, :discarded_at
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_04_29_070135) do
|
||||
ActiveRecord::Schema[7.0].define(version: 2024_01_18_094253) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "btree_gist"
|
||||
enable_extension "pg_trgm"
|
||||
|
@ -984,12 +984,10 @@ ActiveRecord::Schema[7.0].define(version: 2024_04_29_070135) do
|
|||
t.bigint "restored_by_id"
|
||||
t.datetime "restored_on", precision: nil
|
||||
t.integer "assets_view_mode", default: 0
|
||||
t.datetime "discarded_at"
|
||||
t.index "trim_html_tags((name)::text) gin_trgm_ops", name: "index_results_on_name", using: :gin
|
||||
t.index ["archived"], name: "index_results_on_archived"
|
||||
t.index ["archived_by_id"], name: "index_results_on_archived_by_id"
|
||||
t.index ["created_at"], name: "index_results_on_created_at"
|
||||
t.index ["discarded_at"], name: "index_results_on_discarded_at"
|
||||
t.index ["last_modified_by_id"], name: "index_results_on_last_modified_by_id"
|
||||
t.index ["my_module_id"], name: "index_results_on_my_module_id"
|
||||
t.index ["restored_by_id"], name: "index_results_on_restored_by_id"
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
"node-gyp": "9.3.1",
|
||||
"node-polyfill-webpack-plugin": "^2.0.1",
|
||||
"pdfjs-dist": "^2.5.207",
|
||||
"postcss": "8.4.31",
|
||||
"postcss": "8.4.32",
|
||||
"postcss-loader": "5.3.0",
|
||||
"puppeteer": "npm:puppeteer-core",
|
||||
"puppeteer-core": "^21.3.8",
|
||||
|
|
67
yarn.lock
67
yarn.lock
|
@ -2414,11 +2414,11 @@ brace-expansion@^2.0.1:
|
|||
balanced-match "^1.0.0"
|
||||
|
||||
braces@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789"
|
||||
integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
|
||||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
fill-range "^7.1.1"
|
||||
|
||||
brorand@^1.0.1, brorand@^1.1.0:
|
||||
version "1.1.0"
|
||||
|
@ -3679,10 +3679,10 @@ file-selector@^0.4.0:
|
|||
dependencies:
|
||||
tslib "^2.0.3"
|
||||
|
||||
fill-range@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
|
||||
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
|
||||
fill-range@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292"
|
||||
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
|
||||
dependencies:
|
||||
to-regex-range "^5.0.1"
|
||||
|
||||
|
@ -5471,10 +5471,10 @@ nanoid@^2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"
|
||||
integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==
|
||||
|
||||
nanoid@^3.3.4, nanoid@^3.3.6:
|
||||
version "3.3.6"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
||||
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
||||
nanoid@^3.3.7:
|
||||
version "3.3.7"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
|
||||
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
|
||||
|
||||
nanoid@^4.0.0:
|
||||
version "4.0.2"
|
||||
|
@ -6163,12 +6163,12 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
|
|||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
postcss@8.4.31:
|
||||
version "8.4.31"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d"
|
||||
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
|
||||
postcss@8.4.32, postcss@^8.1.10, postcss@^8.4.19:
|
||||
version "8.4.32"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.32.tgz#1dac6ac51ab19adb21b8b34fd2d93a86440ef6c9"
|
||||
integrity sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==
|
||||
dependencies:
|
||||
nanoid "^3.3.6"
|
||||
nanoid "^3.3.7"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
|
@ -6180,24 +6180,6 @@ postcss@^7.0.1:
|
|||
picocolors "^0.2.1"
|
||||
source-map "^0.6.1"
|
||||
|
||||
postcss@^8.1.10:
|
||||
version "8.4.26"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.26.tgz#1bc62ab19f8e1e5463d98cf74af39702a00a9e94"
|
||||
integrity sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==
|
||||
dependencies:
|
||||
nanoid "^3.3.6"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
postcss@^8.4.19:
|
||||
version "8.4.21"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
|
||||
integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
|
||||
dependencies:
|
||||
nanoid "^3.3.4"
|
||||
picocolors "^1.0.0"
|
||||
source-map-js "^1.0.2"
|
||||
|
||||
prelude-ls@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||
|
@ -6309,8 +6291,19 @@ punycode@^2.1.0, punycode@^2.1.1:
|
|||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
|
||||
integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
|
||||
|
||||
puppeteer-core@^21.3.8, "puppeteer@npm:puppeteer-core":
|
||||
name puppeteer
|
||||
puppeteer-core@^21.3.8:
|
||||
version "21.3.8"
|
||||
resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-21.3.8.tgz#7ac4879c9f73e8426431d8ca4c58680e517a4b08"
|
||||
integrity sha512-yv12E/+zZ7Lei5tJB4sUkSrsuqKibuYpYxLGbmtLUjjYIqGE5HKz9OUI2I/RFHEvF+pHi2bTbv5bWydeCGJ6Mw==
|
||||
dependencies:
|
||||
"@puppeteer/browsers" "1.7.1"
|
||||
chromium-bidi "0.4.31"
|
||||
cross-fetch "4.0.0"
|
||||
debug "4.3.4"
|
||||
devtools-protocol "0.0.1179426"
|
||||
ws "8.14.2"
|
||||
|
||||
"puppeteer@npm:puppeteer-core":
|
||||
version "21.3.8"
|
||||
resolved "https://registry.yarnpkg.com/puppeteer-core/-/puppeteer-core-21.3.8.tgz#7ac4879c9f73e8426431d8ca4c58680e517a4b08"
|
||||
integrity sha512-yv12E/+zZ7Lei5tJB4sUkSrsuqKibuYpYxLGbmtLUjjYIqGE5HKz9OUI2I/RFHEvF+pHi2bTbv5bWydeCGJ6Mw==
|
||||
|
|
Loading…
Reference in a new issue