Commit 4eb57b21 authored by hh1966's avatar hh1966
Browse files

Move Export/Import API to CollectionAPI and Export/Import Fetcher to CollectionsFetcher

parent 056fe057
Pipeline #43637 failed with stage
......@@ -123,6 +123,4 @@ class API < Grape::API
mount Chemotion::MessageAPI
mount Chemotion::AdminAPI
mount Chemotion::EditorAPI
mount Chemotion::ExportAPI
mount Chemotion::ImportAPI
end
......@@ -345,6 +345,117 @@ module Chemotion
end
namespace :exports do
desc "Create export job"
params do
requires :collections, type: Array[Integer]
requires :format, type: String
requires :nested, type: Boolean
end
post do
collection_ids = params[:collections].uniq
format = params[:format]
nested = params[:nested] == true
# check if the user is allowed to export these collections
collection_ids.each do |collection_id|
begin
collection = Collection.belongs_to_or_shared_by(current_user.id, current_user.group_ids).find(collection_id)
rescue ActiveRecord::RecordNotFound
error!('401 Unauthorized', 401)
end
end
# create an id for the export
export_id = SecureRandom.uuid
# create the lock file
lock_file_path = Export::ExportCollections.lock_file_path(export_id, format)
File.open(lock_file_path, 'w') {}
# run the asyncronous export job and return its id to the client
ExportCollectionsJob.perform_later(export_id, collection_ids, format, nested)
# return the export_id to the client
return {:export_id => export_id}
end
desc "Poll export job"
params do
requires :id, type: String
end
get '/:id' do
export_id = params[:id]
# look for the lock file file
['json', 'zip'].each do |format|
file_path = Export::ExportCollections.file_path(export_id, format)
lock_file_path = Export::ExportCollections.lock_file_path(export_id, format)
if File.exist?(file_path) and !File.exist?(lock_file_path)
return {
:status => 'COMPLETED',
:url => Export::ExportCollections.file_url(export_id, format)
}
elsif File.exist?(lock_file_path)
return {:status => 'EXECUTING'}
end
end
error! :not_found, 404
end
end
namespace :imports do
desc "Create export job"
params do
requires :file, type: File
end
post do
# create an id for the import
import_id = SecureRandom.uuid
# create the `tmp/imports/` if it does not exist yet
import_path = Import::ImportCollections.import_path
FileUtils.mkdir_p(import_path) unless Dir.exist?(import_path)
# store the file as `tmp/imports/<import_id>.zip`
file_path = Import::ImportCollections.zip_file_path(import_id)
File.open(file_path, 'wb') do |file|
file.write(params[:file][:tempfile].read)
end
# run the asyncronous import job
ImportCollectionsJob.perform_later(import_id, current_user.id)
# return the import_id to the client
return {:import_id => import_id}
end
desc "Poll import job"
params do
requires :id, type: String
end
get '/:id' do
import_id = params[:id]
# look for the lock file file
file_path = Import::ImportCollections.zip_file_path(import_id)
if File.exist?(file_path)
return {
:status => 'EXECUTING',
}
end
error! :not_found, 404
end
end
end
end
end
module Chemotion
class ExportAPI < Grape::API
resource :exports do
desc "Create export job"
params do
requires :collections, type: Array[Integer]
requires :format, type: String
requires :nested, type: Boolean
end
post do
collection_ids = params[:collections].uniq
format = params[:format]
nested = params[:nested] == true
# check if the user is allowed to export these collections
collection_ids.each do |collection_id|
begin
collection = Collection.belongs_to_or_shared_by(current_user.id, current_user.group_ids).find(collection_id)
rescue ActiveRecord::RecordNotFound
error!('401 Unauthorized', 401)
end
end
# create an id for the export
export_id = SecureRandom.uuid
# create the lock file
lock_file_path = Export::ExportCollections.lock_file_path(export_id, format)
File.open(lock_file_path, 'w') {}
# run the asyncronous export job and return its id to the client
ExportCollectionsJob.perform_later(export_id, collection_ids, format, nested)
# return the export_id to the client
return {:export_id => export_id}
end
desc "Poll export job"
params do
requires :id, type: String
end
get '/:id' do
export_id = params[:id]
# look for the lock file file
['json', 'zip'].each do |format|
file_path = Export::ExportCollections.file_path(export_id, format)
lock_file_path = Export::ExportCollections.lock_file_path(export_id, format)
if File.exist?(file_path) and !File.exist?(lock_file_path)
return {
:status => 'COMPLETED',
:url => Export::ExportCollections.file_url(export_id, format)
}
elsif File.exist?(lock_file_path)
return {:status => 'EXECUTING'}
end
end
error! :not_found, 404
end
end
end
end
module Chemotion
class ImportAPI < Grape::API
resource :imports do
desc "Create export job"
params do
requires :file, type: File
end
post do
# create an id for the import
import_id = SecureRandom.uuid
# create the `tmp/imports/` if it does not exist yet
import_path = Import::ImportCollections.import_path
FileUtils.mkdir_p(import_path) unless Dir.exist?(import_path)
# store the file as `tmp/imports/<import_id>.zip`
file_path = Import::ImportCollections.zip_file_path(import_id)
File.open(file_path, 'wb') do |file|
file.write(params[:file][:tempfile].read)
end
# run the asyncronous import job
ImportCollectionsJob.perform_later(import_id, current_user.id)
# return the import_id to the client
return {:import_id => import_id}
end
desc "Poll import job"
params do
requires :id, type: String
end
get '/:id' do
import_id = params[:id]
# look for the lock file file
file_path = Import::ImportCollections.zip_file_path(import_id)
if File.exist?(file_path)
return {
:status => 'EXECUTING',
}
end
error! :not_found, 404
end
end
end
end
......@@ -154,6 +154,24 @@ class CollectionActions {
downloadReportReaction(reactionId){
Utils.downloadFile({contents: "api/v1/reports/excel_reaction?id=" + reactionId});
}
exportCollectionsToFile(params) {
return (dispatch) => { CollectionsFetcher.createExportJob(params)
.then((result) => {
dispatch(result);
}).catch((errorMessage) => {
console.log(errorMessage);
});};
}
importCollectionsFromFile(params) {
return (dispatch) => { CollectionsFetcher.createImportJob(params)
.then((result) => {
dispatch(result);
}).catch((errorMessage) => {
console.log(errorMessage);
});};
}
}
export default alt.createActions(CollectionActions);
......@@ -15,8 +15,6 @@ 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';
......@@ -199,24 +197,6 @@ class ElementActions {
});};
}
exportCollectionsToFile(params) {
return (dispatch) => { ExportCollectionsFetcher.createJob(params)
.then((result) => {
dispatch(result);
}).catch((errorMessage) => {
console.log(errorMessage);
});};
}
importCollectionsFromFile(params) {
return (dispatch) => { ImportCollectionsFetcher.createJob(params)
.then((result) => {
dispatch(result);
}).catch((errorMessage) => {
console.log(errorMessage);
});};
}
// -- Samples --
fetchSampleById(id) {
......
......@@ -2,13 +2,14 @@ import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { Dropdown, Button, MenuItem, Glyphicon } from 'react-bootstrap';
import CollectionActions from '../actions/CollectionActions';
import ElementActions from '../actions/ElementActions';
import UIActions from '../actions/UIActions';
import ModalImport from './ModalImport';
import ModalImportChemScanner from './ModalImportChemScanner';
import ModalExport from './ModalExport';
import ModalReactionExport from './ModalReactionExport';
import ModalCollectionExport from './ModalCollectionExport';
import ModalExportCollection from './ModalExportCollection';
import ModalImportCollection from './ModalImportCollection';
const ExportImportButton = ({ isDisabled, updateModalProps, customClass }) => (
......@@ -112,8 +113,8 @@ const exportReactionFunction = (updateModalProps) => {
const exportCollectionFunction = (updateModalProps) => {
const title = "Export Collections to ZIP archive";
const component = ModalCollectionExport;
const action = ElementActions.exportCollectionsToFile;
const component = ModalExportCollection;
const action = CollectionActions.exportCollectionsToFile;
const listSharedCollections = false;
const modalProps = {
......@@ -130,7 +131,7 @@ const exportCollectionFunction = (updateModalProps) => {
const importCollectionFunction = (updateModalProps) => {
const title = "Import Collections from ZIP archive";
const component = ModalImportCollection;
const action = ElementActions.importCollectionsFromFile;
const action = CollectionActions.importCollectionsFromFile;
const listSharedCollections = false;
const modalProps = {
......
......@@ -3,7 +3,7 @@ import {Button, ButtonToolbar} from 'react-bootstrap';
import UIStore from './../stores/UIStore';
import NotificationActions from '../actions/NotificationActions';
export default class ModalCollectionExport extends React.Component {
export default class ModalExportCollection extends React.Component {
constructor(props) {
super(props);
this.state = {}
......
import 'whatwg-fetch';
import BaseFetcher from './BaseFetcher';
import CollectionActions from '../actions/CollectionActions';
import NotificationActions from '../actions/NotificationActions';
export default class CollectionsFetcher {
static takeOwnership(params) {
let sync = params.isSync ? "syncC" : "c"
......@@ -279,4 +282,117 @@ export default class CollectionsFetcher {
return promise;
}
static createExportJob(params) {
let promise = fetch('/api/v1/collections/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(() => {
CollectionsFetcher.pollExportJob(json.export_id)
}, 1000);
return json;
}).catch((errorMessage) => {
console.log(errorMessage);
});
return promise;
}
static pollExportJob(exportId) {
let promise = fetch(`/api/v1/collections/exports/${exportId}`, {
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(() => {
CollectionsFetcher.pollExportJob(exportId);
}, 1000);
} else if (json.status == 'COMPLETED') {
// remove the notification
NotificationActions.removeByUid('export_collections')
// download the file, headers will prevent the browser from reloading the page
window.location.href = json.url;
}
}).catch((errorMessage) => {
console.log(errorMessage);
});
return promise;
}
static createImportJob(params) {
var data = new FormData();
data.append("file", params.file);
let promise = fetch('/api/v1/collections/imports/', {
credentials: 'same-origin',
method: 'POST',
body: data
}).then((response) => {
return response.json()
}).then((json) => {
// after a short delay, start polling
setTimeout(() => {
CollectionsFetcher.pollImportJob(json.import_id)
}, 1000);
return json;
}).catch((errorMessage) => {
console.log(errorMessage);
});
return promise;
}
static pollImportJob(importId) {
let promise = fetch(`/api/v1/collections/imports/${importId}`, {
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(() => {
CollectionsFetcher.pollImportJob(importId);
}, 1000);
} else {
// remove the notification
NotificationActions.removeByUid('import_collections')
// reload the unshared collections
CollectionActions.fetchUnsharedCollectionRoots()
}
}).catch((errorMessage) => {
console.log(errorMessage);
});
return promise;
}
}
import 'whatwg-fetch';
import NotificationActions from '../actions/NotificationActions';
export default class ExportCollectionsFetcher {
static createJob(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(() => {
ExportCollectionsFetcher.pollJob(json.export_id)
}, 1000);
return json;
}).catch((errorMessage) => {
console.log(errorMessage);
});
return promise;
}
static pollJob(exportId) {
let promise = fetch(`/api/v1/exports/${exportId}`, {
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(() => {
ExportCollectionsFetcher.pollJob(exportId);
}, 1000);
} else if (json.status == 'COMPLETED') {
// remove the notification
NotificationActions.removeByUid('export_collections')
// download the file, headers will prevent the browser from reloading the page
window.location.href = json.url;
}
}).catch((errorMessage) => {
console.log(errorMessage);
});
return promise;
}
}
import 'whatwg-fetch';
import CollectionActions from '../actions/CollectionActions';
import NotificationActions from '../actions/NotificationActions';
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) => {
// after a short delay, start polling
setTimeout(() => {
ImportCollectionsFetcher.pollJob(json.import_id)
}, 1000);
return json;
}).catch((errorMessage) => {
console.log(errorMessage);
});
return promise;
}
static pollJob(importId) {
let promise = fetch(`/api/v1/imports/${importId}`, {
credentials: 'same-origin',
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}).then((response) => {
return response.json()
}).then((json) => {