Commit 3c91bb3e authored by hh1966's avatar hh1966
Browse files

Add front end code for export and implement polling

parent 08a02d34
......@@ -5,24 +5,44 @@ module Chemotion
before do
# TODO: validate collection_id, check permissions
# handle nested collections
@collection_ids = params[:collection_id]
@collection_ids = params[:collections]
@format = params[:format]
end
desc "Export collections as json"
desc "Poll export job"
params do
requires :collection_id, type: Array[Integer]
requires :id, type: String
end
post 'json/' do
ExportCollectionsJob.perform_later('json', @collection_ids)
get '/:id' do
job_id = params[:id]
# look for the export file
['json', 'zip'].each do |fmt|
file_name = File.join('public', fmt, "#{job_id}.#{fmt}")
lock_file_name = file_name + '.lock'
if File.exist?(file_name) and !File.exist?(lock_file_name)
return {
:status => 'COMPLETED',
:url => "/#{fmt}/#{job_id}.#{fmt}"
}
elsif File.exist?(lock_file_name)
return {:status => 'EXECUTING'}
end
end
error! :not_found, 404
end
desc "Export collections as zip"
desc "Create export job"
params do
requires :collection_id, type: Array[Integer]
requires :collections, type: Array[Integer]
requires :format, type: String
end
post 'zip/' do
ExportCollectionsJob.perform_later('zip', @collection_ids)
post do
ExportCollectionsJob.perform_later(@format, @collection_ids)
end
end
end
end
......@@ -8,6 +8,7 @@ import ModalImport from './ModalImport';
import ModalImportChemScanner from './ModalImportChemScanner';
import ModalExport from './ModalExport';
import ModalReactionExport from './ModalReactionExport';
import ModalCollectionExport from './ModalCollectionExport';
const ExportImportButton = ({ isDisabled, updateModalProps, customClass }) => (
<Dropdown id='export-dropdown'>
......@@ -28,6 +29,11 @@ const ExportImportButton = ({ isDisabled, updateModalProps, customClass }) => (
title='Import from spreadsheet or sdf'>
Import samples to collection
</MenuItem>
<MenuItem divider />
<MenuItem onSelect={() => exportCollectionFunction(updateModalProps)} disabled={isDisabled}
title='Export collections'>
Export collections
</MenuItem>
{/* <MenuItem onSelect={() => importChemScannerFunction(updateModalProps)} disabled={isDisabled} */}
{/* title='Import from Docs'> */}
{/* Import elements from Docs */}
......@@ -99,4 +105,15 @@ const exportReactionFunction = (updateModalProps) => {
updateModalProps(modalProps);
}
const exportCollectionFunction = (updateModalProps) => {
const component = ModalCollectionExport;
const modalProps = {
show: true,
title: "Collection Export",
component,
customModal: "exportModal"
};
updateModalProps(modalProps);
}
export default ExportImportButton
import React from 'react';
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) {
super(props);
this.state = {}
this.handleClick = this.handleClick.bind(this)
}
buttonBar() {
const { onHide } = this.props;
return (
<ButtonToolbar>
<div className="pull-right">
<ButtonToolbar>
<Button bsStyle="primary" onClick={onHide}>Cancel</Button>
<Button bsStyle="warning" id="md-export-dropdown"
title="Export as ZIP file (incl. attachments)" onClick={this.handleClick}>
Export ZIP
</Button>
</ButtonToolbar>
</div>
</ButtonToolbar>
)
}
handleClick() {
const uiState = UIStore.getState();
const userState = UserStore.getState();
const { onHide } = this.props;
onHide();
exportCollections(uiState, userState);
}
render() {
const onChange = (v) => this.setState(
previousState => {return { ...previousState, value: v }}
)
return (
<div>
{this.buttonBar()}
</div>
)
}
}
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 {
static createDownloadJob(params) {
let promise = fetch('/api/v1/exports/', {
credentials: 'same-origin',
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(params)
}).then((response) => {
return response.json()
}).then((json) => {
// after a short delay, start polling
setTimeout(() => {
ExportFetcher.pollDownloadJob(json.job_id)
}, 1000);
}).catch((errorMessage) => {
console.log(errorMessage);
});
return promise;
}
static pollDownloadJob(jobId) {
let promise = fetch(`/api/v1/exports/${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.pollDownloadJob(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;
}
}
......@@ -3,7 +3,10 @@ class ExportCollectionsJob < ActiveJob::Base
# rescue_from(ActiveRecord::RecordNotFound) do; end
def perform(format, collection_ids)
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?
[
......@@ -14,10 +17,12 @@ class ExportCollectionsJob < ActiveJob::Base
File.delete(file_name) if File.exist?(file_name)
end
end
end
# prepare the collections for export
export = Export::ExportCollection.new
export.prepare_data collection_ids
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?
......@@ -25,12 +30,17 @@ class ExportCollectionsJob < ActiveJob::Base
File.write('public/json/uuid.json', export.uuids.to_json())
File.write('public/json/attachments.json', export.attachments.to_json())
end
end
case format
def perform(fmt, collection_ids)
# prepare the collections for export
export = Export::ExportCollection.new
export.prepare_data collection_ids
case fmt
when 'json'
# write the json file public/json/
json_file = File.join('public', 'json', "#{self.job_id}.json")
File.write(json_file, export.to_json())
File.write(file_name(fmt, self.job_id), export.to_json())
when 'zip'
# create a zip buffer
zip = Zip::OutputStream.write_buffer do |zip|
......@@ -47,8 +57,17 @@ class ExportCollectionsJob < ActiveJob::Base
zip.rewind
# write the zip file to public/zip/
zip_file = File.join('public', 'zip', "#{self.job_id}.zip")
File.write(zip_file, zip.read)
File.write(file_name(fmt, self.job_id), zip.read)
end
end
private
def file_name(fmt, job_id)
return File.join('public', fmt, "#{job_id}.#{fmt}")
end
def lock_file_name(fmt, job_id)
return file_name(fmt, job_id) + '.lock'
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