Commit 19fdf394 authored by hh1966's avatar hh1966
Browse files

Add Import API and front-end code for Import

parent 3c91bb3e
......@@ -124,4 +124,5 @@ class API < Grape::API
mount Chemotion::AdminAPI
mount Chemotion::EditorAPI
mount Chemotion::ExportAPI
mount Chemotion::ImportAPI
end
module Chemotion
class ImportAPI < Grape::API
resource :imports do
post do
# ensure the tmp/imports directory is there
directory = File.join('tmp', 'import')
FileUtils.mkdir_p(directory) unless File.directory?(directory)
# store the file in the tmp/imports dir
file_path = File.join(directory, params[:file][:filename])
File.open(file_path, 'wb') do |file|
file.write(params[:file][:tempfile].read)
end
# run the asyncronous import job
ImportCollectionsJob.perform_later(file_path, current_user.id)
end
end
end
end
......@@ -15,6 +15,8 @@ import ResearchPlansFetcher from '../fetchers/ResearchPlansFetcher';
import SearchFetcher from '../fetchers/SearchFetcher';
import DeviceFetcher from '../fetchers/DeviceFetcher';
import ContainerFetcher from '../fetchers/ContainerFetcher';
import ExportCollectionsFetcher from '../fetchers/ExportCollectionsFetcher';
import ImportCollectionsFetcher from '../fetchers/ImportCollectionsFetcher';
import Sample from '../models/Sample';
import Reaction from '../models/Reaction';
......@@ -197,6 +199,24 @@ class ElementActions {
});};
}
exportCollectionsToFile(params) {
return (dispatch) => { ExportCollectionsFetcher.createJob(params)
.then((result) => {
dispatch(result);
}).catch((errorMessage) => {
console.log(errorMessage);
});};
}
importCollectionsFromFile(params) {
console.log(params);
return (dispatch) => { ImportCollectionsFetcher.createJob(params)
.then((result) => {
dispatch(result);
}).catch((errorMessage) => {
console.log(errorMessage);
});};
}
// -- Samples --
......
......@@ -31,9 +31,13 @@ const ExportImportButton = ({ isDisabled, updateModalProps, customClass }) => (
</MenuItem>
<MenuItem divider />
<MenuItem onSelect={() => exportCollectionFunction(updateModalProps)} disabled={isDisabled}
title='Export collections'>
title='Export collections as ZIP archive'>
Export collections
</MenuItem>
<MenuItem onSelect={() => importCollectionFunction(updateModalProps)} disabled={isDisabled}
title='Import collections as ZIP archive'>
Import collections
</MenuItem>
{/* <MenuItem onSelect={() => importChemScannerFunction(updateModalProps)} disabled={isDisabled} */}
{/* title='Import from Docs'> */}
{/* Import elements from Docs */}
......@@ -106,14 +110,37 @@ const exportReactionFunction = (updateModalProps) => {
}
const exportCollectionFunction = (updateModalProps) => {
const title = "Export Collections to ZIP archive";
const component = ModalCollectionExport;
const action = ElementActions.exportCollectionsToFile;
const listSharedCollections = false;
const modalProps = {
show: true,
title: "Collection Export",
title,
component,
customModal: "exportModal"
action,
listSharedCollections,
};
updateModalProps(modalProps);
}
const importCollectionFunction = (updateModalProps) => {
const title = "Import Collections from ZIP archive";
const component = ModalImport;
const action = ElementActions.importCollectionsFromFile;
const listSharedCollections = false;
const modalProps = {
show: true,
title,
component,
action,
listSharedCollections,
};
updateModalProps(modalProps);
};
export default ExportImportButton
......@@ -3,7 +3,6 @@ import {Button, ButtonToolbar, Radio, FormGroup} from 'react-bootstrap';
import PropTypes from 'prop-types';
import UIStore from './../stores/UIStore';
import UserStore from './../stores/UserStore';
import ExportFetcher from './../fetchers/ExportFetcher';
export default class ModalCollectionExport extends React.Component {
constructor(props) {
......@@ -31,10 +30,15 @@ export default class ModalCollectionExport extends React.Component {
handleClick() {
const uiState = UIStore.getState();
const userState = UserStore.getState();
const { onHide } = this.props;
const { onHide, action } = this.props;
let params = {
format: 'zip',
collections: [uiState.currentCollection.id],
}
action(params);
onHide();
exportCollections(uiState, userState);
}
render() {
......@@ -52,11 +56,3 @@ export default class ModalCollectionExport extends React.Component {
ModalCollectionExport.propTypes = {
onHide: PropTypes.func,
}
const exportCollections = (uiState, userState) => {
console.log('hey');
ExportFetcher.createDownloadJob({
collections: [uiState.currentCollection.id],
format: 'zip'
});
}
import 'whatwg-fetch';
import _ from 'lodash';
export default class ExportFetcher {
export default class ExportCollectionsFetcher {
static createDownloadJob(params) {
static createJob(params) {
let promise = fetch('/api/v1/exports/', {
credentials: 'same-origin',
......@@ -18,7 +17,7 @@ export default class ExportFetcher {
}).then((json) => {
// after a short delay, start polling
setTimeout(() => {
ExportFetcher.pollDownloadJob(json.job_id)
ExportCollectionsFetcher.pollJob(json.job_id)
}, 1000);
}).catch((errorMessage) => {
console.log(errorMessage);
......@@ -27,7 +26,7 @@ export default class ExportFetcher {
return promise;
}
static pollDownloadJob(jobId) {
static pollJob(jobId) {
let promise = fetch(`/api/v1/exports/${jobId}`, {
credentials: 'same-origin',
......@@ -42,7 +41,7 @@ export default class ExportFetcher {
if (json.status == 'EXECUTING') {
// continue polling
setTimeout(() => {
ExportFetcher.pollDownloadJob(jobId);
ExportCollectionsFetcher.pollJob(jobId);
}, 4000);
} else if (json.status == 'COMPLETED') {
// download the file, headers will prevent the browser from reloading the page
......
import 'whatwg-fetch';
export default class ImportCollectionsFetcher {
static createJob(params) {
var data = new FormData();
data.append("file", params.file);
let promise = fetch('/api/v1/imports/', {
credentials: 'same-origin',
method: 'POST',
body: data
}).then((response) => {
return response.json()
}).then((json) => {
console.log('ok!');
// // after a short delay, start polling
// setTimeout(() => {
// ExportFetcher.pollJob(json.job_id)
// }, 1000);
}).catch((errorMessage) => {
console.log(errorMessage);
});
return promise;
}
// static pollJob(jobId) {
// let promise = fetch(`/api/v1/imports/${jobId}`, {
// credentials: 'same-origin',
// method: 'GET',
// headers: {
// 'Accept': 'application/json',
// 'Content-Type': 'application/json'
// }
// }).then((response) => {
// return response.json()
// }).then((json) => {
// if (json.status == 'EXECUTING') {
// // continue polling
// setTimeout(() => {
// ExportFetcher.pollJob(jobId);
// }, 4000);
// } else if (json.status == 'COMPLETED') {
// // download the file, headers will prevent the browser from reloading the page
// window.location.href = json.url;
// }
// }).catch((errorMessage) => {
// console.log(errorMessage);
// });
// return promise;
// }
}
\ No newline at end of file
......@@ -5,36 +5,18 @@ class ExportCollectionsJob < ActiveJob::Base
before_enqueue do |job|
# create lock file
File.open(lock_file_name(job.arguments.first, job.job_id), "w") {}
# remove debug output
unless Rails.env.production?
[
'public/json/data.json',
'public/json/uuid.json',
'public/json/attachments.json'
].each do |file_name|
File.delete(file_name) if File.exist?(file_name)
end
end
File.open(lock_file_name(job.arguments.first, job.job_id), 'w') {}
end
after_perform do |job|
# remove lock file
lock_file_name = lock_file_name(job.arguments.first, job.job_id)
File.delete(lock_file_name) if File.exist?(lock_file_name)
# debug output
unless Rails.env.production?
File.write('public/json/data.json', export.data.to_json())
File.write('public/json/uuid.json', export.uuids.to_json())
File.write('public/json/attachments.json', export.attachments.to_json())
end
end
def perform(fmt, collection_ids)
# prepare the collections for export
export = Export::ExportCollection.new
export = Export::ExportCollections.new
export.prepare_data collection_ids
case fmt
......@@ -45,12 +27,12 @@ class ExportCollectionsJob < ActiveJob::Base
# create a zip buffer
zip = Zip::OutputStream.write_buffer do |zip|
# write the json file into the zip file
zip.put_next_entry 'data.json'
zip.put_next_entry File.join(self.job_id, 'data.json')
zip.write export.to_json()
# write all attachemnts into an attachments directory
export.attachments.each do |attachment|
zip.put_next_entry File.join('attachments', attachment.filename)
zip.put_next_entry File.join(self.job_id, 'attachments', attachment.filename)
zip.write attachment.read_file
end
end
......
class ImportCollectionsJob < ActiveJob::Base
queue_as :import_collections
def perform(file_path, current_user_id)
import = Import::ImportCollections.new(file_path, current_user_id)
import.process
end
end
module Export
class ExportCollection
class ExportCollections
attr_reader :data
attr_reader :uuids
......
require 'json'
module Import
class ImportCollections
def initialize(file_path, current_user_id)
@file_path = file_path
@current_user_id = current_user_id
@directory = File.join(
File.dirname(@file_path),
File.basename(file_path, File.extname(file_path))
)
@data = nil
@uuids = {}
@attachments = []
end
def process
extract
read
import
self
end
def extract()
# # make sure that no old directory exists
# FileUtils.remove_dir(@directory) if File.directory?(@directory)
destination = File.dirname(@file_path)
Zip::File.open(@file_path) do |zip_file|
zip_file.each do |f|
fpath = File.join(destination, f.name)
fdir = File.dirname(fpath)
FileUtils.mkdir_p(fdir) unless File.directory?(fdir)
zip_file.extract(f, fpath) unless File.exist?(fpath)
end
end
# delete the zip file
# File.delete(file_path) if File.exist?(file_path)
end
def read
# open and read the data.json
file_name = File.join(@directory, 'data.json')
File.open(file_name) do |f|
@data = JSON.parse(f.read())
end
end
def import
ActiveRecord::Base.transaction do
@data.each do |item|
puts item
end
end
end
end
end
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment