mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Implement inventories export backend logic [SCI-8444] (#5578)
This commit is contained in:
parent
0e14b7de8a
commit
f0c2624179
|
@ -382,6 +382,16 @@ class RepositoriesController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def export_repositories
|
||||
repositories = Repository.viewable_by_user(current_user, current_team).where(id: params[:repository_ids])
|
||||
if repositories.present?
|
||||
RepositoriesExportJob.perform_later(repositories.pluck(:id), current_user, current_team)
|
||||
render json: { message: t('zip_export.export_request_success') }
|
||||
else
|
||||
render json: { message: t('zip_export.export_error') }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def assigned_my_modules
|
||||
my_modules = MyModule.joins(:repository_rows).where(repository_rows: { repository: @repository })
|
||||
.readable_by_user(current_user).distinct
|
||||
|
|
98
app/jobs/repositories_export_job.rb
Normal file
98
app/jobs/repositories_export_job.rb
Normal file
|
@ -0,0 +1,98 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RepositoriesExportJob < ApplicationJob
|
||||
include StringUtility
|
||||
|
||||
def perform(repository_ids, user, team)
|
||||
@user = user
|
||||
@team = team
|
||||
@repositories = Repository.viewable_by_user(@user, @team).where(id: repository_ids).order(:id)
|
||||
zip_input_dir = FileUtils.mkdir_p(Rails.root.join("tmp/temp_zip_#{Time.now.to_i}")).first
|
||||
zip_dir = FileUtils.mkdir_p(Rails.root.join('tmp/zip-ready')).first
|
||||
|
||||
zip_name = "inventories_export_#{Time.now.utc.strftime('%F_%H-%M-%S_UTC')}.zip"
|
||||
full_zip_name = File.join(zip_dir, zip_name)
|
||||
|
||||
fill_content(zip_input_dir)
|
||||
ZipExport.transaction do
|
||||
@zip_export = ZipExport.create!(user: @user)
|
||||
@zip_export.zip!(zip_input_dir, full_zip_name)
|
||||
@zip_export.zip_file.attach(io: File.open(full_zip_name), filename: zip_name)
|
||||
generate_notification
|
||||
end
|
||||
ensure
|
||||
FileUtils.rm_rf([zip_input_dir, full_zip_name], secure: true)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fill_content(tmp_dir)
|
||||
# Create team dir
|
||||
team_path = "#{tmp_dir}/#{to_filesystem_name(@team.name)}"
|
||||
FileUtils.mkdir_p(team_path)
|
||||
@repositories.each_with_index do |repository, idx|
|
||||
save_repository_to_csv(team_path, repository, idx)
|
||||
end
|
||||
end
|
||||
|
||||
def save_repository_to_csv(path, repository, idx)
|
||||
repository_name = "#{to_filesystem_name(repository.name)} (#{idx})"
|
||||
|
||||
# Attachments dir
|
||||
relative_attachments_path = "#{repository_name} attachments"
|
||||
attachments_path = "#{path}/#{relative_attachments_path}"
|
||||
FileUtils.mkdir_p(attachments_path)
|
||||
|
||||
# CSV file
|
||||
csv_file = FileUtils.touch("#{path}/#{repository_name}.csv").first
|
||||
|
||||
# Define headers and columns IDs
|
||||
col_ids = [-3, -4, -5, -6] + repository.repository_columns.map(&:id)
|
||||
|
||||
# Define callback function for file name
|
||||
assets = {}
|
||||
asset_counter = 0
|
||||
handle_name_func = lambda do |asset|
|
||||
file_name = append_file_suffix(asset.file_name, "_#{asset_counter}").to_s
|
||||
|
||||
# Save pair for downloading it later
|
||||
assets[asset] = "#{attachments_path}/#{file_name}"
|
||||
|
||||
asset_counter += 1
|
||||
relative_path = "#{relative_attachments_path}/#{file_name}"
|
||||
return "=HYPERLINK(\"#{relative_path}\", \"#{relative_path}\")"
|
||||
end
|
||||
|
||||
# Generate CSV
|
||||
csv_data = RepositoryZipExport.to_csv(repository.repository_rows, col_ids, @user, repository, handle_name_func)
|
||||
File.binwrite(csv_file, csv_data)
|
||||
|
||||
# Save all attachments (it doesn't work directly in callback function
|
||||
assets.each do |asset, asset_path|
|
||||
asset.file.open do |file|
|
||||
FileUtils.mv(file.path, asset_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def append_file_suffix(file_name, suffix)
|
||||
file_name = to_filesystem_name(file_name)
|
||||
ext = File.extname(file_name)
|
||||
File.basename(file_name, ext) + suffix + ext
|
||||
end
|
||||
|
||||
def generate_notification
|
||||
notification = Notification.create!(
|
||||
type_of: :deliver,
|
||||
title: I18n.t('zip_export.notification_title'),
|
||||
message: "<a data-id='#{@zip_export.id}' " \
|
||||
"data-turbolinks='false' " \
|
||||
"href='#{Rails.application
|
||||
.routes
|
||||
.url_helpers
|
||||
.zip_exports_download_export_all_path(@zip_export)}'>" \
|
||||
"#{@zip_export.zip_file_name}</a>"
|
||||
)
|
||||
UserNotification.create!(notification: notification, user: @user)
|
||||
end
|
||||
end
|
|
@ -13,16 +13,15 @@ class TeamZipExport < ZipExport
|
|||
).first
|
||||
zip_dir = FileUtils.mkdir_p(File.join(Rails.root, 'tmp/zip-ready')).first
|
||||
|
||||
zip_name = "projects_export_#{Time.now.strftime('%F_%H-%M-%S_UTC')}.zip"
|
||||
zip_name = "projects_export_#{Time.now.utc.strftime('%F_%H-%M-%S_UTC')}.zip"
|
||||
full_zip_name = File.join(zip_dir, zip_name)
|
||||
zip_file = File.new(full_zip_name, 'w+')
|
||||
|
||||
fill_content(zip_input_dir, data, type, options)
|
||||
zip!(zip_input_dir, zip_file)
|
||||
self.zip_file.attach(io: File.open(zip_file), filename: zip_name)
|
||||
zip!(zip_input_dir, full_zip_name)
|
||||
zip_file.attach(io: File.open(full_zip_name), filename: zip_name)
|
||||
generate_notification(user) if save
|
||||
ensure
|
||||
FileUtils.rm_rf([zip_input_dir, zip_file], secure: true)
|
||||
FileUtils.rm_rf([zip_input_dir, full_zip_name], secure: true)
|
||||
end
|
||||
|
||||
handle_asynchronously :generate_exportable_zip,
|
||||
|
@ -320,38 +319,4 @@ class TeamZipExport < ZipExport
|
|||
|
||||
csv_file_path
|
||||
end
|
||||
|
||||
# Recursive zipping
|
||||
def zip!(input_dir, output_file)
|
||||
files = Dir.entries(input_dir)
|
||||
|
||||
# Don't zip current/above directory
|
||||
files.delete_if { |el| ['.', '..'].include?(el) }
|
||||
|
||||
Zip::File.open(output_file.path, Zip::File::CREATE) do |zipfile|
|
||||
write_entries(input_dir, files, '', zipfile)
|
||||
end
|
||||
end
|
||||
|
||||
# A helper method to make the recursion work.
|
||||
def write_entries(input_dir, entries, path, io)
|
||||
entries.each do |e|
|
||||
zip_file_path = path == '' ? e : File.join(path, e)
|
||||
disk_file_path = File.join(input_dir, zip_file_path)
|
||||
puts 'Deflating ' + disk_file_path
|
||||
if File.directory?(disk_file_path)
|
||||
io.mkdir(zip_file_path)
|
||||
subdir = Dir.entries(disk_file_path)
|
||||
|
||||
# Remove current/above directory to prevent infinite recursion
|
||||
subdir.delete_if { |el| ['.', '..'].include?(el) }
|
||||
|
||||
write_entries(input_dir, subdir, zip_file_path, io)
|
||||
else
|
||||
io.get_output_stream(zip_file_path) do |f|
|
||||
f.write(File.open(disk_file_path, 'rb').read)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,19 +36,27 @@ class ZipExport < ApplicationRecord
|
|||
zip_file.blob&.filename&.to_s
|
||||
end
|
||||
|
||||
def zip!(input_dir, output_file)
|
||||
entries = Dir.glob('**/*', base: input_dir)
|
||||
Zip::File.open(output_file, create: true) do |zipfile|
|
||||
entries.each do |entry|
|
||||
zipfile.add(entry, "#{input_dir}/#{entry}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def generate_exportable_zip(user, data, type, options = {})
|
||||
I18n.backend.date_format = user.settings[:date_format] || Constants::DEFAULT_DATE_FORMAT
|
||||
zip_input_dir = FileUtils.mkdir_p(File.join(Rails.root, "tmp/temp_zip_#{Time.now.to_i}")).first
|
||||
tmp_zip_dir = FileUtils.mkdir_p(File.join(Rails.root, 'tmp/zip-ready')).first
|
||||
tmp_zip_name = "export_#{Time.now.strftime('%F %H-%M-%S_UTC')}.zip"
|
||||
tmp_zip_file = File.new(File.join(tmp_zip_dir, tmp_zip_name), 'w+')
|
||||
tmp_full_zip_name = File.join(tmp_zip_dir, "export_#{Time.now.strftime('%F %H-%M-%S_UTC')}.zip")
|
||||
|
||||
fill_content(zip_input_dir, data, type, options)
|
||||
zip!(zip_input_dir, tmp_zip_file)
|
||||
zip_file.attach(io: File.open(tmp_zip_file), filename: tmp_zip_name)
|
||||
zip_file.attach(io: File.open(tmp_full_zip_name), filename: tmp_zip_name)
|
||||
generate_notification(user) if save
|
||||
ensure
|
||||
FileUtils.rm_rf([zip_input_dir, tmp_zip_file], secure: true)
|
||||
FileUtils.rm_rf([zip_input_dir, tmp_full_zip_name], secure: true)
|
||||
end
|
||||
|
||||
handle_asynchronously :generate_exportable_zip
|
||||
|
@ -60,14 +68,14 @@ class ZipExport < ApplicationRecord
|
|||
.delete_expired_export(id)
|
||||
end
|
||||
|
||||
def method_missing(m, *args, &block)
|
||||
puts 'Method is missing! To use this zip_export you have to ' \
|
||||
'define a method: generate_( type )_zip.'
|
||||
object.public_send(m, *args, &block)
|
||||
def method_missing(method_name, *args, &block)
|
||||
return super unless method_name.to_s.start_with?('generate_')
|
||||
|
||||
raise StandardError, 'Method is missing! To use this zip_export you have to define a method: generate_( type )_zip.'
|
||||
end
|
||||
|
||||
def respond_to_missing?(method_name, include_private = false)
|
||||
method_name.to_s.start_with?(' generate_') || super
|
||||
method_name.to_s.start_with?('generate_') || super
|
||||
end
|
||||
|
||||
def fill_content(dir, data, type, options = {})
|
||||
|
@ -89,16 +97,6 @@ class ZipExport < ApplicationRecord
|
|||
UserNotification.create(notification: notification, user: user)
|
||||
end
|
||||
|
||||
def zip!(input_dir, output_file)
|
||||
files = Dir.entries(input_dir)
|
||||
files.delete_if { |el| el == '..' || el == '.' }
|
||||
Zip::File.open(output_file.path, Zip::File::CREATE) do |zipfile|
|
||||
files.each do |filename|
|
||||
zipfile.add(filename, input_dir + '/' + filename)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def generate_repositories_zip(tmp_dir, data, _options = {})
|
||||
file = FileUtils.touch("#{tmp_dir}/export.csv").first
|
||||
File.open(file, 'wb') { |f| f.write(data) }
|
||||
|
|
|
@ -57,7 +57,7 @@ module RepositoryZipExport
|
|||
when -8
|
||||
I18n.t('repositories.table.archived_on')
|
||||
else
|
||||
column = RepositoryColumn.find_by_id(c_id)
|
||||
column = repository.repository_columns.find_by(id: c_id)
|
||||
column ? column.name : nil
|
||||
end
|
||||
end
|
||||
|
@ -88,8 +88,7 @@ module RepositoryZipExport
|
|||
.find_by(repository_column_id: c_id)
|
||||
|
||||
if cell
|
||||
if cell.value_type == 'RepositoryAssetValue' &&
|
||||
handle_file_name_func
|
||||
if cell.value_type == 'RepositoryAssetValue' && handle_file_name_func
|
||||
handle_file_name_func.call(cell.value.asset)
|
||||
else
|
||||
SmartAnnotations::TagToText.new(
|
||||
|
|
|
@ -219,6 +219,7 @@ Rails.application.routes.draw do
|
|||
member do
|
||||
post 'parse_sheet', defaults: { format: 'json' }
|
||||
post 'export_repository', to: 'repositories#export_repository'
|
||||
post 'export_repositories'
|
||||
post 'export_projects'
|
||||
get 'sidebar'
|
||||
get 'export_projects_modal'
|
||||
|
|
Loading…
Reference in a new issue